// ==UserScript==
// @name                    Play-With-MPV
// @name:zh                 使用 MPV 播放
// @namespace               https://github.com/LuckyPuppy514
// @version                 4.0.0
// @author                  LuckyPuppy514
// @copyright               2023, Grant LuckyPuppy514 (https://github.com/LuckyPuppy514)
// @license                 MIT
// @description             使用 mpv 播放网页中的视频，并支持 potplayer 及自定义播放器
// @homepage                https://github.com/LuckyPuppy514/Play-With-MPV
// @icon                    https://www.lckp.top/gh/LuckyPuppy514/pic-bed/common/mpv.png
// @match                   https://www.bilibili.com/bangumi/play/*
// @match                   https://www.bilibili.com/video/*
// @match                   https://www.bilibili.com/festival/*
// @match                   https://www.bilibili.com/list/*
// @match                   https://live.bilibili.com/*
// @match                   https://www.ixigua.com/*
// @match                   https://yun.nxflv.com/?url=*
// @match                   https://ddys.art/*
// @match                   https://ddys.pro/*
// @include                 *://*.libvio.*
// @include                 https://*.chinaeast2.cloudapp.chinacloudapi.cn*/*
// @match                   https://*.cfnode1.xyz/*?url=*
// @include                 https://www.nivod*.tv/*
// @match                   https://www.pkmkv.com/py/*
// @match                   https://www.pkmkv.com/addons/dplayer/?url=*
// @match                   https://www.btnull.org/py/*
// @match                   https://www.btnull.to/py/*
// @match                   https://www.btnull.nu/py/*
// @match                   https://www.btnull.in/py/*
// @include                 *://www.*dm.com/play/*
// @match                   *://www.ntdm8.com/play/*
// @match                   https://danmu.yhdmjx.com/*?url=*
// @match                   https://dick.xfani.com/watch/*
// @match                   https://dick.xfani.com/addons/dp/player/*
// @match                   https://player.moedot.net/player/*
// @match                   https://m3.moedot.net/muiplayer/?url=*
// @match                   https://www.mgnacg.com/bangumi/*
// @match                   https://play.mknacg.top:8585/*
// @match                   https://www.omofun.top/index.php/vod/play/id/*
// @match                   https://*.omofun.top/?url=*
// @match                   https://spdcat.net/vodplay/*
// @match                   https://spdcat.net/addons/dp/player/*
// @match                   http://www.dm88.me/player/*
// @match                   https://jianghu.live2008.com/*?url=*
// @match                   https://www.kk151.com/play/*
// @match                   https://jx.m3u8.tv/jiexi/?url=*
// @match                   https://jx.wolongzywcdn.com:65/m3u8.php?url=*
// @match                   https://www.m3u8.tv.cdn.8old.cn/jx.php?url=*
// @match                   https://jx.wujinkk.com/dplayer/?url=*
// @match                   https://www.ikdmjx.com/?url=*
// @match                   https://hls.kuaibofang.com/?url=*
// @match                   https://jx.jxbdzyw.com/m3u8/?url=*
// @match                   https://hdzyk.com/?m=*
// @match                   https://1080zyk1.com/?m=*
// @match                   https://1080zyk2.com/?m=*
// @match                   https://1080zyk3.com/?m=*
// @match                   https://1080zyk4.com/?m=*
// @match                   https://1080zyk5.com/?m=*
// @match                   https://vip.zykbf.com/?url=*
// @match                   https://*.yzzy-tv1.com/*
// @match                   https://*.yzzy-tv-cdn.com/*
// @match                   https://www.bdys10.com/*
// @match                   https://www.haitu.tv/*
// @include                 *://*alist*
// @include                 *://*:5244*
// @match                   *://*/*.mp4
// @match                   *://*/*.mkv
// @match                   *://*/*.flv
// @match                   https://www.dora-family.com/Resource:TV
// @match                   https://www.olehdtv.com/*
// @match                   *://tkznp.com/vodplay/*
// @match                   *://www.tkznp.com/vodplay/*
// @match                   *://www.tkznp1.com/vodplay/*
// @match                   *://www.tkznp2.com/vodplay/*
// @match                   *://www.tkznp3.com/vodplay/*
// @match                   *://www.tkznp4.com/vodplay/*
// @match                   *://www.tkznp5.com/vodplay/*
// @match                   *://www.tkznp6.com/vodplay/*
// @match                   https://vip.ckllk.com/?url=*
// @match                   https://www.anfuns.cc/play/*
// @match                   https://www.anfuns.cc/vapi/*
// @match                   https://www.youtube.com/*
// @match                   https://odysee.com/*
// @match                   https://rumble.com/*
// @match                   https://www.bitchute.com/*
// @match                   https://ani.gamer.com.tw/animeVideo.php?sn=*
// @match                   https://hanime1.me/watch?v=*
// @match                   https://jable.tv/videos/*
// @match                   https://ok.ru/*
// @match                   https://tver.jp/*
// @match                   https://www.lckp.top/play-with-mpv/index.html
// @match                   https://www.douyin.com/
// @match                   https://www.douyin.com/video/*
// @match                   https://www.douyin.com/discover?modal_id=*
// @match                   https://www.mengfan.tv/play/*
// @match                   https://www.mfan.tv/play/*
// @match                   https://video1.beijcloud.com/player/?url=*
// @match                   https://www.tucao.cam/play/*
// @match                   https://mypikpak.com/drive/*
// @match                   https://www.icourse163.org/learn/*
// @match                   https://www.iole.tv/*
// @match                   https://www.zhihu.com/zvideo/*
// @match                   *://www.susudm8.com/*
// @match                   *://susudyy.com/*
// @match                   *://buding3.com/*
// @match                   *://buding6.com/*
// @match                   *://v2.shenjw.com:*/wap.php?url=*
// @match                   *://u88.xigua88ok.com:*/wap.php?url=*
// @match                   *://test3.gqyy8.com:*/f/aliplayer.php?url=*
// @match                   *://v.mksec.cn/*
// @match                   https://tgbook.coolkv.com/*play*
// @include                 *://*dsh*.com/*
// @match                   https://www.twitch.tv/*
// @match                   https://jiohub.top/watch/*
// @match                   https://www.agemys.org/play/*
// @include                 https://vip.sp-flv.com:*?url=*
// @match                   https://anime.girigirilove.com/*
// @match                   https://www.cycdm01.top/*
// @match                   https://player.cycdm01.top/?url=*
// @connect                 api.bilibili.com
// @connect                 api.live.bilibili.com
// @require                 https://unpkg.com/jquery@3.2.1/dist/jquery.min.js
// @require                 https://unpkg.com/md5@2.3.0/dist/md5.min.js
// @grant                   GM_setValue
// @grant                   GM_getValue
// @run-at                  document-body
// ==/UserScript==

"use strict";

const INFO = `

▶️🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽◀️
▶️                                                       ◀️
▶️                     Play-With-MPV                     ◀️
▶️                                                       ◀️
▶️    https://github.com/LuckyPuppy514/Play-With-MPV     ◀️
▶️                                                       ◀️
▶️                  © 2023 LuckyPuppy514                 ◀️
▶️                                                       ◀️
▶️🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼◀️

`;
// gm key
const KEY = {
    config: "config",
};
// 默认配置
const DEFAULT_CONFIG = {
    player: "mpv",
    mpv: {
        path: "",
        regVersion: "20230514",
    },
    potplayer: {
        path: "",
        regVersion: "20230514",
    },
    proxy: "",
    bestQuality: "2160p",
    bilibiliCodecs: 12,
    playAuto: 0,
    closeAuto: 0,
    syncStartTime: 0,
    subtitlePrefer: "zh-Hans",
    customplayer: {
        name: "customplayer",
        path: "",
        params: {
            videoUrl: "iina://weblink?url=${EvideoUrl}",
            audioUrl: "",
            subtitleUrl: "",
            title: "",
            startTime: "",
            referer: "",
            origin: "&mpv_http-header-fields=origin%3A%20${Eorigin}",
            proxy: "",
            other: "",
        },
    },
    transform: {
        xOffset: 0,
        yOffset: 0,
    },
    version: "20230922",
};
var currentConfig;
// 视频链接匹配正则
const VIDEO_URL_REGEX =
    /https?:\/\/(?![^"^']*http)[^"^']+(\.|%2e)(m3u8|m3u|mp4|mkv|flv|avi)(\?[\w&=-]+|)/g;
// 父子页面方法名
const METHOD = {
    pause: "PAUSE",
    report: "REPORT",
};
// 时间 ms
const TIME = {
    out: 3000,
    toast: 3500,
    refresh: 600,
    reportInterval: 600,
    pauseInterval: 2000,
    showButton: 5000,
};
// 尝试次数
var tryTime = 0;
const TRY_TIME = {
    maxPause: 5,
    maxParse: 8,
};
// 播放器配置
const PLAYER = {
    mpv: {
        name: "mpv",
        params: {
            videoUrl: 'mpv://"${videoUrl}"',
            audioUrl: ' --audio-file="${audioUrl}"',
            subtitleUrl: ' --sub-file="${subtitleUrl}"',
            title: ' --force-media-title="${title}"',
            startTime: " --start=${startTime}",
            referer: ' --http-header-fields="referer: ${referer}"',
            origin: ' --http-header-fields="origin: ${origin}"',
            proxy: " --http-proxy=${proxy} --ytdl-raw-options=proxy=[${proxy}]",
            other: " ${other}",
        },
    },
    potplayer: {
        name: "potplayer",
        params: {
            videoUrl: "potplayer://${videoUrl} /current",
            subtitleUrl: ' /sub="${subtitleUrl}"',
            title: ' /title="${title}"',
            startTime: " /seek=${startTime}",
            referer: ' /referer="${referer}"',
            origin: ' /headers="origin: ${origin}"',
            proxy: ' /user_agent="${proxy}"',
        },
    },
    customplayer: {
        name: "customplayer",
        params: undefined,
    },
};
// 页面信息
var page = {
    host: undefined,
    url: undefined,
    isFullScreen: false,
};
// 处理器
var handler;
// 前缀
const PREFIX = "pwm";
// 组件 id
const ID = {
    loadingDiv: `${PREFIX}-loading-div`,
    toastDiv: `${PREFIX}-toast-div`,
    buttonDiv: `${PREFIX}-button-div`,
    infoButton: `${PREFIX}-info-button`,
    infoDiv: `${PREFIX}-info-div`,
    infoTable: `${PREFIX}-info-table`,
    mpvPlayButton: `${PREFIX}-mpv-play-button`,
    potplayerPlayButton: `${PREFIX}-potplayer-play-button`,
    customplayerPlayButton: `${PREFIX}-customplayer-play-button`,
    settingButton: `${PREFIX}-setting-button`,
    settingDiv: `${PREFIX}-setting-div`,
    settingTable: `${PREFIX}-setting-table`,
    playerRadio: `${PREFIX}-player-radio`,
    softwarePathInput: `${PREFIX}-software-path-input`,
    proxyInput: `${PREFIX}-proxy-input`,
    bestQualityRadio: `${PREFIX}-best-quality-radio`,
    bilibiliCodecsRadio: `${PREFIX}-bilibili-codecs-radio`,
    saveButton: `${PREFIX}-save-button`,
    downloadButton: `${PREFIX}-download-button`,
    deleteButton: `${PREFIX}-delete-button`,
    playAutoInput: `${PREFIX}-play-auto-input`,
    closeAutoInput: `${PREFIX}-close-auto-input`,
    syncStartTimeInput: `${PREFIX}-sync-start-time-input`,
    syncStartTimeSpan: `${PREFIX}-sync-start-time-span`,
    infoDiv: `${PREFIX}-info-div`,
    infoTable: `${PREFIX}-info-table`,
    subtitlePreferRadio: `${PREFIX}-subtitle-prefer-radio`,
    customplayerSettingButton: `${PREFIX}-customplayer-setting-button`,
    customplayerSettingTable: `${PREFIX}-customplayer-setting-table`,
    videoUrlParamInput: `${PREFIX}-video-url-param-input`,
    audioUrlParamInput: `${PREFIX}-audio-url-param-input`,
    subtitleUrlParamInput: `${PREFIX}-subtitle-url-param-input`,
    titleParamInput: `${PREFIX}-title-param-input`,
    startTimeParamInput: `${PREFIX}-start-time-param-input`,
    proxyParamInput: `${PREFIX}-proxy-param-input`,
    refererParamInput: `${PREFIX}-referer-param-input`,
    originParamInput: `${PREFIX}-origin-param-input`,
    nxParserIframe: `${PREFIX}-nx-parser-iframe`,
};
// 组件 class
const CLASS = {
    button: `${PREFIX}-button`,
    titleSpan: `${PREFIX}-title-span-class`,
    titleTd: `${PREFIX}-title-td-class`,
    closeButton: `${PREFIX}-cloase-button-class`,
    tipSpan: `${PREFIX}-tip-span-class`,
    footerSpan: `${PREFIX}-footer-span-class`,
    switchLabel: `${PREFIX}-switch-label-class`,
    sliderSpan: `${PREFIX}-slider-span-class`,
    roundSpan: `${PREFIX}-round-span-class`,
    readOnly: `${PREFIX}-read-only-class`,
    footerA: `${PREFIX}-footer-a-class`,
    infoInput: `${PREFIX}-info-input-class`,
};
// 消息类型
const TOAST_TYPE = {
    info: "info",
    warn: "warn",
    error: "error",
};
// 图标
const ICON_BASE64 = {
    customplayer:
        "url('')",
    back: "url('')",
};
const CSS = `
#${ID.loadingDiv} {
    display: none;
    position: fixed;
    bottom: 50%;
    left: 50%;
    z-index: 999999;
    transform: translate(-50%, -50%);
    background-color: rgba(255, 255, 255, 0);
}
#${ID.loadingDiv} .spinner {
    width: 40px;
    height: 40px;
    background-color: rgba(255, 255, 255, 1);
    -webkit-animation: sk-rotateplane 1.2s infinite ease-in-out;
    animation: sk-rotateplane 1.2s infinite ease-in-out;
}
@-webkit-keyframes sk-rotateplane {
    0% {
        -webkit-transform: perspective(120px)
    }

    50% {
        -webkit-transform: perspective(120px) rotateY(180deg)
    }

    100% {
        -webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg)
    }
}

@keyframes sk-rotateplane {
    0% {
        transform: perspective(120px) rotateX(0deg) rotateY(0deg);
        -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg)
    }

    50% {
        transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
        -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg)
    }

    100% {
        transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
        -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
    }
}
#${ID.toastDiv} {
    display: none;
    position: fixed;
    bottom: 80%;
    left: 50%;
    max-width: 60%;
    min-width: 150px;
    padding: 0 14px;
    height: 40px;
    color: rgb(255, 255, 255);
    line-height: 40px;
    text-align: center;
    border-radius: 4px;
    transform: translate(-50%, -50%);
    z-index: 9999999999;
    background: rgba(0, 255, 0, .9);
    font-size: 14px;
    font-weight: blod;
}
/* 按钮 */
#${ID.buttonDiv} {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    cursor: move;
    z-index: 99999;
    width: fit-content;
    height: 70px;
}
#${ID.buttonDiv}:hover .${CLASS.button} {
    visibility: visible !important;
}
.${CLASS.button} {
    position: static;
    cursor: pointer;
    z-index: 99999;
    border: none;
    border-radius: 50%;
    background-size: cover;
    background-color: rgba(255, 255, 255, 0);
}
.${CLASS.button}:hover {
    transform: scale(1.1);
}
#${ID.potplayerPlayButton} {
    width: 42px;
    height: 42px;
    margin: auto 0;
    background-image: url()
}
#${ID.mpvPlayButton} {
    width: 50px;
    height: 50px;
    margin: auto 0;
    background-image: url();
}
#${ID.customplayerPlayButton} {
    width: 46px;
    height: 46px;
    margin: auto 0;
    background-image: url()
}
/* 设置 */
#${ID.settingButton} {
    width: 28px;
    height: 28px;
    background-image: url();
}
#${ID.settingDiv},
#${ID.infoDiv} {
    position: fixed;
    top: 40%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 999999999;
    width: 900px;
    height: 520px;
    background-color: rgb(65, 146, 247);
    display: none;
    flex-direction: column;
	border-radius: 6px;
	align-items: center;
    color: rgba(0, 0, 0, .7);
    font-family: "微软雅黑";
}
#${ID.infoDiv} {
    background-color: rgb(234, 122, 153) !important;
}
#${ID.infoTable},
#${ID.settingTable},
#${ID.customplayerSettingTable} {
    width: 800px;
    height: 460px;
    border-radius: 5px !important;
    border: 3px solid rgba(255, 255, 255, 1) !important;
    text-align: left;
    border-collapse: unset !important;
    display: flex;
    justify-content: center;
    padding-top: 3px;
    padding-bottom: 12px;
    line-height: 20px !important;
    font-weight: normal !important;
}
#${ID.customplayerSettingTable} {
    display: none;
}
#${ID.infoTable} td,
#${ID.settingTable} td,
#${ID.customplayerSettingTable} td {
    font-size: 14px;
    border: 0px solid rgba(255, 255, 255, 0.5);
    padding: 19px 0px 0px 0px !important;
}
#${ID.infoTable} td {
    padding-top: 16.5px !important;
}
.${CLASS.titleSpan} {
    padding-top: 12.5px !important;;
    padding-bottom: 12.5px !important;;
    font-size: 16px;
    font-weight: bold;
    color: rgba(255, 255, 255, 1) !important;
}
.${CLASS.closeButton} {
    position: absolute;
    top: 4px;
    right: 8px;
    height: 25px;
    width: 25px;
    border: none;
    font-size: 17px !important;
    font-weight: normal !important;
    background-color: rgba(0, 0, 0, 0);
    line-height: 0px;
    border-radius: 3px;
    transform: scale(1.32, 1);
    color: rgba(255, 255, 255, 1);
}
.${CLASS.closeButton}:hover {
    font-size: 20px;
    background-color: rgba(255, 255, 255, .5);
    cursor: pointer;
}
.${CLASS.tipSpan} {
    font-size: 12px;
    color: yellow;
    position: fixed;
}
.${CLASS.titleTd} {
    position: relative;
    width: 80px;
    height: 30px;
    border: none;
    font-size: 14px;
    text-align: center;
    color: rgba(255, 255, 255, 1) !important;
    cursor: default;
}
#${ID.infoTable} input,
#${ID.settingTable} input,
#${ID.customplayerSettingTable} input {
    font-size: 12px !important;
    width: 500px;
    height: 26px;
    border: none;
    outline: none;
    text-indent: 5px;
    padding: 0px !important;
    border-radius: 0px !important;
    color: rgba(0, 0, 0, 1);
    background-color: rgba(255, 255, 255, 1);
    cursor: auto;
    display: flex !important;
    margin-top: 1px !important;
    margin-bottom: 1px !important;
    border-collapse: unset !important;
}
#${ID.infoTable} input:hover,
#${ID.settingTable} input:hover,
#${ID.customplayerSettingTable} input:hover,
#${ID.infoTable} input:focus-visible,
#${ID.settingTable} input:focus-visible,
#${ID.customplayerSettingTable} input:focus-visible {
    box-shadow: none;
}
#${ID.settingTable} input::placeholder,
#${ID.customplayerSettingTable} input::placeholder {
    font-size: 12px;
    color: rgba(0, 0, 0, .3);
}
#${ID.saveButton} {
    font-size: 14px;
    margin-left: 83px;
    width: 300px;
    height: 30px;
    border: none;
    border-radius: 3px;
    color: rgba(255, 255, 255, 1);
    background-color: rgba(0, 255, 0, .7);
}
#${ID.downloadButton} {
    font-size: x-small;
    margin-left: 10px;
    width: 100px;
    height: 30px;
    border: none;
    border-radius: 3px;
    color: rgba(255, 255, 255, 1);
    background-color: rgba(0, 255, 0, .7);
}
#${ID.deleteButton} {
    text-decoration: none;
    font-size: x-small;
    width: 80px;
    height: 30px;
    border: none;
    border-radius: 3px;
    color: rgba(255, 255, 255, 1);
    background-color: rgba(0,0,0,0);
}
#${ID.saveButton}:hover,
#${ID.downloadButton}:hover {
    opacity: .8;
    background-color: rgba(0, 255, 0, .8);
    cursor: pointer;
}
#${ID.deleteButton}:hover {
    opacity: .8;
    background-color: rgba(0,0,0,0);
    cursor: pointer;
}
.${CLASS.footerSpan} {
    margin-top: 8px !important;
    margin-bottom: 8px !important;
    color: rgba(255, 255, 255, 1);
}
#${ID.infoDiv} a,
.${CLASS.footerSpan} a {
    color: rgba(255, 255, 255, 1);
    text-decoration: none;
    font-size: 14px !important;
    font-weight: normal !important;
    margin-bottom: 1px;
    display: inline-block;
}
/* switch */
.${CLASS.switchLabel} {
    position: relative;
    display: inline-block;
    width: 50px;
    height: 21px;
    margin-top: 3px;
}
.${CLASS.switchLabel} input {
    opacity: 0;
    width: 0 !important;
    height: 0 !important;
}
.${CLASS.sliderSpan} {
    position: absolute;
    cursor: pointer;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(255, 255, 255, .6);
    -webkit-transition: .4s;
    transition: .4s;
}
.${CLASS.sliderSpan}:before {
    position: absolute;
    content: "";
    height: 13px;
    width: 13px;
    left: 4px;
    bottom: 4px;
    background-color: rgba(255, 255, 255, 1);
    -webkit-transition: .4s;
    transition: .4s;
}
#${ID.settingDiv} input:checked + .${CLASS.sliderSpan} {
    background-color: rgba(0, 255, 0, .7);
}
#${ID.settingDiv} input:focus + .${CLASS.sliderSpan} {
    box-shadow: 0 0 1px rgba(0, 255, 0, .7);
}
#${ID.settingDiv} input:checked + .${CLASS.sliderSpan}:before {
    -webkit-transform: translateX(29px);
    -ms-transform: translateX(29px);
    transform: translateX(29px);
}
.${CLASS.sliderSpan}.${CLASS.roundSpan} {
    border-radius: 34px;
}
.${CLASS.sliderSpan}.${CLASS.roundSpan}:before {
    border-radius: 50%;
}
.${CLASS.readOnly} {
    color: rgba(255, 255, 255, .3) !important;
    background-color: rgba(0, 0, 0, .3) !important;
    cursor: default !important;
    opacity: 1 !important;
}
.${CLASS.readOnly}::placeholder {
    color: rgba(255, 255, 255, .3) !important;
}
#${ID.infoButton} {
    width: 25px;
    height: 25px;
    background-image: url();
}
#${ID.settingDiv} .tabs {
    display: flex;
    width: 501px;
    height: 28px;
}
#${ID.settingDiv} .tabs>.tab {
    flex: 1;
    display: flex;
    border: 1px solid rgb(65,146,247);
    margin: 0;
}
#${ID.settingDiv} .tabs>.tab:after {
    background: rgba(0, 0, 0, 0);
    color: rgba(0, 0, 0, 0) !important;
}
#${ID.settingDiv} .tab>.tab-input {
    width: 0 !important;
    height: 0 !important;
    margin: 0 !important;
    display: none !important;
}
#${ID.settingDiv} .tab>.tab-box {
    padding: 4px 0px 0.5px 0px;
    width: 100%;
    height: 22px;
    text-align: center;
    transition: 0.3s;
    background: rgba(255, 255, 255, 1);
    font-size: 12px;
    font-weight: normal !important;
    display: table !important;
    color: #000000B3;
}
#${ID.settingDiv} .tab>.tab-box:hover {
    opacity: .8;
    cursor: pointer;
}
#${ID.settingDiv} .tab>.tab-input:checked+.tab-box {
    color: rgba(255, 255, 255, 1);
    background: rgba(0, 255, 0, .7);
}
#${ID.customplayerSettingButton}:hover:after,
#${ID.settingDiv} .${CLASS.titleTd}:hover:after,
.${CLASS.footerA}:hover:after {
    position: absolute;
    font-size: 12px;
    left: 0px;
    top: -3px;
    padding: 5px 5px 5px 5px !important;
    background-color: rgba(0, 0, 0, 0.6);
    color: rgba(255, 255, 255, 1);
    content: attr(data-tip);
    text-align: center;
    z-index: 999999;
    width: auto !important;
    height: auto !important;
    white-space: nowrap;
}
#${ID.customplayerSettingButton}:hover:after {
    left: -10px !important;
    top: -28px !important;
}
.${CLASS.footerA} {
    position: relative;
}
.${CLASS.footerA}:hover:after {
    left: 0px !important;
    top: -27px !important;
}
#${ID.customplayerSettingButton} {
    position: absolute;
    right: 122px;
    top: 76px;
    width: 22px;
    height: 22px;
    border: none;
    cursor: pointer;
    background-image: ${ICON_BASE64.customplayer};
    z-index: 999999;
}
`;
const HTML = `
<div id="${ID.loadingDiv}">
    <div class="spinner"></div>
</div>
<div id="${ID.toastDiv}"></div>

<div id="${ID.buttonDiv}" draggable="true">
    <button id="${ID.infoButton}" class="${CLASS.button}"></button>
    <button id="${ID.potplayerPlayButton}" class="${CLASS.button}"></button>
    <button id="${ID.mpvPlayButton}" class="${CLASS.button}"></button>
    <button id="${ID.customplayerPlayButton}" class="${CLASS.button}"></button>
    <button id="${ID.settingButton}" class="${CLASS.button}"></button>
</div>

<div id="${ID.infoDiv}">
    <span class="${CLASS.titleSpan}">Play-With-MPV<button class="${CLASS.closeButton}">X</button></span>
    <table id="${ID.infoTable}">
        <tr>
            <td colspan="4" style="text-align: center; color: white; font-size: 16px;">本 页 视 频 信 息</td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}">视频标题</td>
            <td colspan="8">
                <input type="text" readonly class="${CLASS.infoInput}">
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}">视频链接</td>
            <td colspan="8">
                <input type="text" readonly class="${CLASS.infoInput}">
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}">音频链接</td>
            <td colspan="8">
                <input type="text" readonly class="${CLASS.infoInput}">
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}">字幕链接</td>
            <td colspan="8">
                <input type="text" readonly class="${CLASS.infoInput}">
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}">referer</td>
            <td colspan="8">
                <input type="text" readonly class="${CLASS.infoInput}">
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}">origin</td>
            <td colspan="8">
                <input type="text" readonly class="${CLASS.infoInput}">
            </td>
        </tr>
        <tr><td></td></tr>
        <tr>
            <td style="text-align: right;">
                <a href="https://greasyfork.org/zh-CN/scripts/444056-play-with-mpv" target="_blank">🆕 升级 🆕</a>
            </td>
            <td style="text-align: right;">
                <a href="https://www.lckp.top/play-with-mpv/index.html" target="_blank">🧭 网站导航 🧭</a>
            </td>
            <td style="text-align: right;">
                <a href="https://github.com/LuckyPuppy514/Play-With-MPV" target="_blank">🌟 项目源码 🌟</a>
            </td>
            <td style="text-align: right;">
                <a href="https://github.com/LuckyPuppy514/Play-With-MPV/issues/new" target="_blank">👻 反馈 👻</a>
            </td>
        </tr>
    </table>
    <span class="${CLASS.footerSpan}">
        <a href="https://greasyfork.org/zh-CN/scripts/444056-play-with-mpv" target="_blank" class="${CLASS.footerA}" data-tip="版本升级"> 🆕 </a>
        <a href="https://github.com/LuckyPuppy514/Play-With-MPV" target="_blank" class="${CLASS.footerA}" data-tip="项目源码"> © 2023 LuckyPuppy514 </a>
        <a href="https://github.com/LuckyPuppy514/Play-With-MPV/issues/new" target="_blank" class="${CLASS.footerA}" data-tip="问题反馈"> 👻 </a>
    </span>
</div>

<div id="${ID.settingDiv}">
    <span class="${CLASS.titleSpan}">Play-With-MPV<button class="${CLASS.closeButton}">X</button></span>
    <button id="${ID.customplayerSettingButton}" class="${CLASS.button}" data-tip="设置自定义播放器"></button>
    <table id="${ID.settingTable}">
        <tr>
            <td class="${CLASS.titleTd}" data-tip="选择 mpv 以外播放器时，部分功能无效">播放软件</td>
            <td colspan="8">
                <div class="tabs">
                    <label class="tab">
                        <input type="radio" name="${ID.playerRadio}" value="${PLAYER.mpv.name}" class="tab-input">
                        <div class="tab-box">mpv</div>
                    </label>
                    <label class="tab">
                        <input type="radio" name="${ID.playerRadio}" value="${PLAYER.potplayer.name}" class="tab-input">
                        <div class="tab-box">potplayer</div>
                    </label>
                    <label class="tab">
                        <input type="radio" name="${ID.playerRadio}" value="${PLAYER.customplayer.name}" class="tab-input">
                        <div class="tab-box">自定义</div>
                    </label>
                </div>
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}" data-tip="mpv.exe 或 PotPlayerMini64.exe 的完整路径">软件路径</td>
            <td colspan="8">
                <div>
                    <input id="${ID.softwarePathInput}" type=text placeholder="请输入软件路径，例如：D://mpvnet//mpvnet.exe">
                </div>
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}"  data-tip="旁路由网关实现代理一般不需要设置">代理设置</td>
            <td colspan="8">
                <div>
                    <input id="${ID.proxyInput}" type=text placeholder="请输入代理地址，例如：http://127.0.0.1:10809">
                </div>
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}" data-tip="仅适用于B站或使用 yt-dlp 解析的网站，例如：油管，OK，TVer 等">最高画质</td>
            <td colspan="8">
                <div class="tabs">
                    <label class="tab">
                        <input type="radio" name="${ID.bestQualityRadio}" value="unlimited" class="tab-input">
                        <div class="tab-box" name="${ID.bestQualityRadio}">无限制</div>
                    </label>
                    <label class="tab">
                        <input type="radio" name="${ID.bestQualityRadio}" value="2160p" class="tab-input">
                        <div class="tab-box" name="${ID.bestQualityRadio}">2160p</div>
                    </label>
                    <label class="tab">
                        <input type="radio" name="${ID.bestQualityRadio}" value="1440p" class="tab-input">
                        <div class="tab-box" name="${ID.bestQualityRadio}">1440p</div>
                    </label>
                    <label class="tab">
                        <input type="radio" name="${ID.bestQualityRadio}" value="1080p" class="tab-input">
                        <div class="tab-box" name="${ID.bestQualityRadio}">1080p</div>
                    </label>
                    <label class="tab">
                        <input type="radio" name="${ID.bestQualityRadio}" value="720p" class="tab-input">
                        <div class="tab-box" name="${ID.bestQualityRadio}">720p</div>
                    </label>
                </div>
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}" data-tip="仅适用于B站">视频编码</td>
            <td colspan="8">
                <div class="tabs">
                    <label class="tab">
                        <input type="radio" name="${ID.bilibiliCodecsRadio}" value="12" class="tab-input">
                        <div class="tab-box" name="${ID.bilibiliCodecsRadio}">HEVC</div>
                    </label>
                    <label class="tab">
                        <input type="radio" name="${ID.bilibiliCodecsRadio}" value="13" class="tab-input">
                        <div class="tab-box" name="${ID.bilibiliCodecsRadio}">AV1</div>
                    </label>
                    <label class="tab">
                        <input type="radio" name="${ID.bilibiliCodecsRadio}" value="7" class="tab-input">
                        <div class="tab-box" name="${ID.bilibiliCodecsRadio}">AVC</div>
                    </label>
                </div>
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}" data-tip="仅适用于B站">首选字幕</td>
            <td colspan="8">
                <div class="tabs">
                    <label class="tab">
                        <input type="radio" name="${ID.subtitlePreferRadio}" value="zh-Hans" class="tab-input">
                        <div class="tab-box" name="${ID.subtitlePreferRadio}">简体</div>
                    </label>
                    <label class="tab">
                        <input type="radio" name="${ID.subtitlePreferRadio}" value="zh-Hant" class="tab-input">
                        <div class="tab-box" name="${ID.subtitlePreferRadio}">繁体</div>
                    </label>
                    <label class="tab">
                        <input type="radio" name="${ID.subtitlePreferRadio}" value="en-US" class="tab-input"=>
                        <div class="tab-box" name="${ID.subtitlePreferRadio}">英语</div>
                    </label>
                    <label class="tab">
                        <input type="radio" name="${ID.subtitlePreferRadio}" value="off" class="tab-input"=>
                        <div class="tab-box" name="${ID.subtitlePreferRadio}">关闭</div>
                    </label>
                </div>
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}" data-tip="解析成功自动播放">自动播放</td>
            <td colspan="2">
                <div>
                    <label class="${CLASS.switchLabel}">
                        <input type="checkbox" id="${ID.playAutoInput}">
                        <span class="${CLASS.sliderSpan} ${CLASS.roundSpan}"></span>
                    </label>
                </div>
            </td>
            <td class="${CLASS.titleTd}" data-tip="播放时自动关闭页面（和自动播放一起开启时修改配置请前往导航页）">自动关闭</td>
            <td colspan="2">
                <div>
                    <label class="${CLASS.switchLabel}">
                        <input type="checkbox" id="${ID.closeAutoInput}">
                        <span class="${CLASS.sliderSpan} ${CLASS.roundSpan}"></span>
                    </label>
                </div>
            </td>
            <td class="${CLASS.titleTd}" data-tip="同步网页播放时间">同步时间</td>
            <td colspan="2">
                <div>
                    <label class="${CLASS.switchLabel}">
                        <input type="checkbox" id="${ID.syncStartTimeInput}">
                        <span class="${CLASS.sliderSpan} ${CLASS.roundSpan}" id="${ID.syncStartTimeSpan}"></span>
                    </label>
                </div>
            </td>
        </tr>
        <tr>
            <td colspan="9">
                <button id="${ID.saveButton}">保存设置</button>
                <button id="${ID.downloadButton}">下载注册表</button>
                <button id="${ID.deleteButton}">删除注册表</button>
            </td>
        </tr>
    </table>
    <table id="${ID.customplayerSettingTable}">
        <tr>
            <td class="${CLASS.titleTd}" data-tip="必填（视频格式：yt-dlp / m3u8 / flv / m4s / mp4 / mkv ... 播放器不支持则无法播放对应格式视频）">视频参数</td>
            <td colspan="8">
                <div>
                    <input id="${ID.videoUrlParamInput}" type=text placeholder='请输入视频参数，例如：mpv://"$\{videoUrl\}"'>
                </div>
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}" data-tip="选填（为空则不支持最高画质和视频编码）">音频参数</td>
            <td colspan="8">
                <div>
                    <input id="${ID.audioUrlParamInput}" type=text placeholder='请输入音频参数，例如： --audio-file="$\{audioUrl\}"'>
                </div>
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}" data-tip="选填（为空则无法加载B站外挂字幕）">字幕参数</td>
            <td colspan="8">
                <div>
                    <input id="${ID.subtitleUrlParamInput}" type=text placeholder='请输入字幕参数，例如： --sub-file="$\{subtitleUrl\}"'>
                </div>
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}" data-tip="选填（为空则无法传递标题）">标题参数</td>
            <td colspan="8">
                <div>
                    <input id="${ID.titleParamInput}" type=text placeholder='请输入标题参数，例如： --force-media-title="$\{title\}"'>
                </div>
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}" data-tip="选填（为空则不支持同步时间）">时间参数</td>
            <td colspan="8">
                <div>
                    <input id="${ID.startTimeParamInput}" type=text placeholder='请输入时间参数，例如： --start=$\{startTime\}'>
                </div>
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}" data-tip="选填（为空则不支持代理设置）">代理参数</td>
            <td colspan="8">
                <div>
                    <input id="${ID.proxyParamInput}" type=text placeholder='请输入代理参数，例如： --http-proxy=$\{proxy\} --ytdl-raw-options=proxy=[$\{proxy\}]'>
                </div>
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}" data-tip="选填（为空则无法观看B站和橘子动漫）">referer</td>
            <td colspan="8">
                <div>
                    <input id="${ID.refererParamInput}" type=text placeholder='请输入 referer，例如： --http-header-fields="referer: $\{referer\}"'>
                </div>
            </td>
        </tr>
        <tr>
            <td class="${CLASS.titleTd}" data-tip="选填（为空则无法观看巴哈姆特）">origin</td>
            <td colspan="8">
                <div>
                    <input id="${ID.originParamInput}" type=text placeholder='请输入 origin，例如： --http-header-fields="origin: $\{origin\}" '>
                </div>
            </td>
        </tr>
    </table>
    <span class="${CLASS.footerSpan}">
        <a href="https://greasyfork.org/zh-CN/scripts/444056-play-with-mpv" target="_blank" class="${CLASS.footerA}" data-tip="版本升级"> 🆕 </a>
        <a href="https://github.com/LuckyPuppy514/Play-With-MPV" target="_blank" class="${CLASS.footerA}" data-tip="项目源码"> © 2023 LuckyPuppy514 </a>
        <a href="https://github.com/LuckyPuppy514/Play-With-MPV/issues/new" target="_blank" class="${CLASS.footerA}" data-tip="问题反馈"> 👻 </a>
    </span>
</div>
`;
const REG = `Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Google\\Chrome]
"ExternalProtocolDialogShowAlwaysOpenCheckbox"=dword:00000001

[HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Microsoft\\Edge]
"ExternalProtocolDialogShowAlwaysOpenCheckbox"=dword:00000001

[HKEY_CLASSES_ROOT\\\${PLAYER_NAME}]
@="\${PLAYER_NAME} Protocol"
"URL Protocol"=""

[HKEY_CLASSES_ROOT\\\${PLAYER_NAME}\\DefaultIcon]
@=""

[HKEY_CLASSES_ROOT\\\${PLAYER_NAME}\\shell]
@=""

[HKEY_CLASSES_ROOT\\\${PLAYER_NAME}\\shell\\open]
@=""

[HKEY_CLASSES_ROOT\\\${PLAYER_NAME}\\shell\\open\\command]
@="C:\\\\Windows\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe -WindowStyle Hidden -Command \\"& {Add-Type -AssemblyName System.Web;$PARAMS=([System.Web.HTTPUtility]::UrlDecode('%1') -replace '^\${PLAYER_NAME}://'); Start-Process -FilePath \\\\\\\"\${SOFTWARE_PATH}\\\\\\\" -ArgumentList $PARAMS}\\""
`;
const REG_DELETE = `Windows Registry Editor Version 5.00
[-HKEY_CLASSES_ROOT\\\${PLAYER_NAME}]
`;
function appendCSS() {
    let css = document.createElement("style");
    css.innerHTML = CSS.trim();
    document.head.appendChild(css);
}
function appendHTML() {
    let div = document.createElement("div");
    div.innerHTML = HTML.trim();
    document.body.appendChild(div);
}
function loading(visiable) {
    let loadingDiv = document.getElementById(ID.loadingDiv);
    if (visiable) {
        loadingDiv.style.display = "flex";
        setTimeout(() => {
            if (loadingDiv.style.display == "flex") {
                document.getElementById(ID.loadingDiv).style.display = "none";
                toast("超时辣 ...... 😓", TOAST_TYPE.error);
            }
        }, TIME.out);
    } else {
        loadingDiv.style.display = "none";
    }
}
function toast(message, type, duration) {
    type = type ? type : TOAST_TYPE.info;
    duration = isNaN(duration) ? TIME.toast : duration;
    let toastDiv = document.getElementById(ID.toastDiv);
    toastDiv.innerHTML = message;
    toastDiv.style.display = "block";
    if (type == TOAST_TYPE.info) {
        toastDiv.style.backgroundColor = "rgba(75, 180 ,54, 1)";
    } else if (type == TOAST_TYPE.warn) {
        toastDiv.style.backgroundColor = "rgba(190, 190, 70, 1)";
    } else if (type == TOAST_TYPE.error) {
        toastDiv.style.backgroundColor = "rgba(210, 51, 35, 1)";
    }
    setTimeout(() => {
        toastDiv.style.display = "none";
    }, duration);
}
function playButtonClick(player) {
    if (playDisabled) {
        return;
    }
    if (currentConfig.player != player) {
        let playAuto = currentConfig.playAuto;
        currentConfig.player = player;
        currentConfig.playAuto = 1;
        GM_setValue(KEY.config, currentConfig);
        setTimeout(() => {
            currentConfig.playAuto = playAuto;
            GM_setValue(KEY.config, currentConfig);
        }, TIME.pauseInterval);
        init();
        return;
    }
    if (
        currentConfig.player == PLAYER.mpv.name ||
        currentConfig.player == PLAYER.potplayer.name
    ) {
        let message = undefined;
        if (!currentConfig[currentConfig.player].path) {
            message = "请先进行设置";
        } else if (!currentConfig[currentConfig.player].regVersion) {
            message = "请先下载注册表";
        } else if (
            currentConfig[currentConfig.player].regVersion !=
            DEFAULT_CONFIG[currentConfig.player].regVersion
        ) {
            message = "注册表有更新，请重新下载注册表";
        }
        if (message) {
            toast(message, TOAST_TYPE.warn);
            settingDiv.style.display = "none";
            settingButton.click();
            return;
        }
    }
    loading(true);
    try {
        playLimit();
        handler.play();
        if (
            currentConfig.closeAuto == 1 &&
            page.url !== "https://www.lckp.top/play-with-mpv/index.html"
        ) {
            setTimeout(() => {
                if (history.length === 1) {
                    window.location.href = "about:blank";
                    window.top.close();
                } else {
                    history.back();
                }
            }, 1000);
        } else {
            handler.pause();
        }
    } catch (error) {
        toast("出错辣 ...... 😓", TOAST_TYPE.error);
        console.log(error);
    }
    loading(false);
}
// 限制播放按钮点击频率
var playDisabled = false;
function playLimit() {
    playDisabled = true;
    setTimeout(() => {
        playDisabled = false;
    }, TIME.pauseInterval);
}
//拖动状态
let isDragging = false;
//设置悬浮窗位置
function setPosOffset(xOffset, yOffset, el) {
    el.style.left = xOffset + "px";
    el.style.bottom = yOffset + "px";
}
function addListener() {
    let buttonDiv = document.getElementById(ID.buttonDiv);
    let mpvPlayButton = document.getElementById(ID.mpvPlayButton);
    let potplayerPlayButton = document.getElementById(ID.potplayerPlayButton);
    let customplayerPlayButton = document.getElementById(
        ID.customplayerPlayButton
    );
    let settingButton = document.getElementById(ID.settingButton);
    let settingDiv = document.getElementById(ID.settingDiv);
    let settingTable = document.getElementById(ID.settingTable);
    let softwarePathInput = document.getElementById(ID.softwarePathInput);
    let proxyInput = document.getElementById(ID.proxyInput);
    let playAutoInput = document.getElementById(ID.playAutoInput);
    let closeAutoInput = document.getElementById(ID.closeAutoInput);
    let syncStartTimeInput = document.getElementById(ID.syncStartTimeInput);
    let syncStartTimeSpan = document.getElementById(ID.syncStartTimeSpan);
    let downloadButton = document.getElementById(ID.downloadButton);
    let deleteButton = document.getElementById(ID.deleteButton);
    let saveButton = document.getElementById(ID.saveButton);
    let closeButtons = document.getElementsByClassName(CLASS.closeButton);
    let infoButton = document.getElementById(ID.infoButton);
    let infoDiv = document.getElementById(ID.infoDiv);
    let customplayerSettingTable = document.getElementById(
        ID.customplayerSettingTable
    );
    let customplayerSettingButton = document.getElementById(
        ID.customplayerSettingButton
    );
    let videoUrlParamInput = document.getElementById(ID.videoUrlParamInput);
    let audioUrlParamInput = document.getElementById(ID.audioUrlParamInput);
    let subtitleUrlParamInput = document.getElementById(
        ID.subtitleUrlParamInput
    );
    let titleParamInput = document.getElementById(ID.titleParamInput);
    let startTimeParamInput = document.getElementById(ID.startTimeParamInput);
    let proxyParamInput = document.getElementById(ID.proxyParamInput);
    let refererParamInput = document.getElementById(ID.refererParamInput);
    let originParamInput = document.getElementById(ID.originParamInput);
    let infoInputs = document.getElementsByClassName(CLASS.infoInput);
    switchStatus(downloadButton, false);

    //处理窗口拖动事件
    let currentX, currentY;
    let initialX, initialY;
    let lastXOffset, lastYOffset;

    // 在dragStart中存储初始的偏移量
    let dragStart = (e) => {
        initialX = e.offsetX;
        initialY = e.offsetY;

        lastXOffset = e.clientX - e.offsetX;
        lastYOffset = e.clientY - e.offsetY;
        isDragging = true;
        // 拖动过程中不隐藏面板
        showPanelForDuration();
        console.log("开始拖动");
    };

    let dragEnd = (e) => {
        currentX = e.clientX - initialX;
        currentY = e.clientY - initialY;

        // 计算相对上一次偏移量的变化值
        let xOffset = currentX - lastXOffset;
        let yOffset = currentY - lastYOffset;

        // 根据变化值更新配置
        currentConfig.transform.xOffset += xOffset;
        currentConfig.transform.yOffset -= yOffset;

        setPosOffset(
            currentConfig.transform.xOffset,
            currentConfig.transform.yOffset,
            buttonDiv
        );
        GM_setValue(KEY.config, currentConfig);
        console.log(
            "存储偏移量：xOffset " +
                currentConfig.transform.xOffset +
                " yOffset " +
                currentConfig.transform.yOffset
        );

        isDragging = false;
    };
    //修改拖动过程中的光标为可放置的样式，但可能会破坏原有的交互
    //document.addEventListener('dragover', (event) => {
    //    event.preventDefault();
    //});
    buttonDiv.addEventListener("dragstart", dragStart);
    buttonDiv.addEventListener("dragend", dragEnd);

    // 播放按钮
    mpvPlayButton.onclick = function () {
        playButtonClick(PLAYER.mpv.name);
    };
    potplayerPlayButton.onclick = function () {
        playButtonClick(PLAYER.potplayer.name);
    };
    customplayerPlayButton.onclick = function () {
        playButtonClick(PLAYER.customplayer.name);
    };
    // 播放快捷键 CTRL + P
    window.onkeydown = async function () {
        if (event.ctrlKey && event.keyCode === 80 && !event.shiftKey) {
            event.preventDefault();
            playButtonClick(currentConfig.player);
        }
    };
    // 设置按钮
    settingButton.onclick = function () {
        let display = settingDiv.style.display;
        if (display == "flex") {
            settingDiv.style.display = "none";
        } else {
            infoDiv.style.display = "none";
            settingDiv.style.display = "flex";
            // 加载配置
            softwarePathInput.value = currentConfig[currentConfig.player].path;
            proxyInput.value = currentConfig.proxy;
            $(
                `input:radio[name="${ID.bestQualityRadio}"][value="${currentConfig.bestQuality}"]`
            ).prop("checked", true);
            $(
                `input:radio[name="${ID.bilibiliCodecsRadio}"][value="${currentConfig.bilibiliCodecs}"]`
            ).prop("checked", true);
            $(
                `input:radio[name="${ID.playerRadio}"][value="${currentConfig.player}"]`
            ).prop("checked", true);
            playAutoInput.checked = currentConfig.playAuto == 1 ? true : false;
            closeAutoInput.checked =
                currentConfig.closeAuto == 1 ? true : false;
            syncStartTimeInput.checked =
                currentConfig.syncStartTime == 1 ? true : false;
            $(
                `input:radio[name="${ID.subtitlePreferRadio}"][value="${currentConfig.subtitlePrefer}"]`
            ).prop("checked", true);
            switchPlayer(
                $(`input:radio[name="${ID.playerRadio}"]:checked`).val()
            );
        }
    };
    // 播放器选择框
    $(`input:radio[name="${ID.playerRadio}"]`).change(function () {
        switchPlayer(this.value);
    });
    // 保存按钮
    saveButton.onclick = function () {
        let playerChecked = $(
            `input:radio[name="${ID.playerRadio}"]:checked`
        ).val();
        if (
            playerChecked == PLAYER.mpv.name ||
            playerChecked == PLAYER.potplayer.name
        ) {
            let oldSoftwarePath = currentConfig[playerChecked].path;
            let newSoftwarePath = softwarePathInput.value;
            if (newSoftwarePath) {
                if (/.*[\u4e00-\u9fa5]+.*/g.test(newSoftwarePath)) {
                    toast("软件路径不能包含中文", TOAST_TYPE.error);
                    return;
                }
                newSoftwarePath = newSoftwarePath.replace(/[\\|/]+/g, "//");
                if (
                    !newSoftwarePath.endsWith(".com") &&
                    !newSoftwarePath.endsWith(".exe")
                ) {
                    if (!newSoftwarePath.endsWith("//")) {
                        newSoftwarePath = newSoftwarePath + "//";
                    }
                    if (playerChecked == PLAYER.mpv.name) {
                        if (
                            newSoftwarePath.toLowerCase().indexOf("mpvnet") !=
                                -1 ||
                            newSoftwarePath.toLowerCase().indexOf("mpv.net") !=
                                -1
                        ) {
                            newSoftwarePath = newSoftwarePath + "mpvnet.exe";
                        } else {
                            newSoftwarePath = newSoftwarePath + "mpv.exe";
                        }
                    } else if (playerChecked == PLAYER.potplayer.name) {
                        newSoftwarePath =
                            newSoftwarePath + "PotPlayerMini64.exe";
                    }
                }
            }
            softwarePathInput.value = newSoftwarePath;
            currentConfig[playerChecked].path = newSoftwarePath;
            switchStatus(
                downloadButton,
                softwarePathInput.value ? true : false
            );
            if (oldSoftwarePath != newSoftwarePath) {
                currentConfig[playerChecked].regVersion = "00000000";
            }
        }
        currentConfig.proxy = proxyInput.value;
        currentConfig.bestQuality = $(
            `input:radio[name="${ID.bestQualityRadio}"]:checked`
        ).val();
        currentConfig.bilibiliCodecs = $(
            `input:radio[name="${ID.bilibiliCodecsRadio}"]:checked`
        ).val();
        currentConfig.player = playerChecked;
        currentConfig.subtitlePrefer = $(
            `input:radio[name="${ID.subtitlePreferRadio}"]:checked`
        ).val();
        currentConfig.playAuto = playAutoInput.checked ? 1 : 0;
        currentConfig.closeAuto = closeAutoInput.checked ? 1 : 0;
        currentConfig.syncStartTime = syncStartTimeInput.checked ? 1 : 0;
        updatePlayButtonVisibility(currentConfig);
        GM_setValue(KEY.config, currentConfig);
        if (playAutoInput.checked && closeAutoInput.checked) {
            toast("保存成功，如需修改配置请前往导航页");
        } else {
            toast("保存成功");
        }
        if (currentConfig.playAuto == 1) {
            playLimit();
        }
        if (
            document.getElementById("iptv") &&
            localStorage.category == "iptv"
        ) {
            localStorage.player = JSON.stringify(PLAYER[currentConfig.player]);
            document.getElementById("iptv").click();
        }
        init();
    };
    // 下载按钮
    downloadButton.onclick = function () {
        let playerChecked = $(
            `input:radio[name="${ID.playerRadio}"]:checked`
        ).val();
        currentConfig[playerChecked].regVersion =
            DEFAULT_CONFIG[playerChecked].regVersion;
        GM_setValue(KEY.config, currentConfig);
        let reg = REG.replace(
            "${SOFTWARE_PATH}",
            currentConfig[playerChecked].path
        );
        reg = reg.replace(/\$\{PLAYER_NAME\}/g, playerChecked);
        let a = document.createElement("a");
        let blob = new Blob([reg], { type: "application/octet-stream" });
        a.href = window.URL.createObjectURL(blob);
        a.download = `${playerChecked}-install.reg`;
        a.click();
    };
    deleteButton.onclick = function () {
        let playerChecked = $(
            `input:radio[name="${ID.playerRadio}"]:checked`
        ).val();
        currentConfig[playerChecked].regVersion = "00000000";
        GM_setValue(KEY.config, currentConfig);
        let reg = REG_DELETE.replace(/\$\{PLAYER_NAME\}/g, playerChecked);
        let a = document.createElement("a");
        let blob = new Blob([reg], { type: "application/octet-stream" });
        a.href = window.URL.createObjectURL(blob);
        a.download = `${playerChecked}-delete.reg`;
        a.click();
    };
    // 关闭按钮
    for (let closeButton of closeButtons) {
        closeButton.onclick = function () {
            settingDiv.style.display = "none";
            infoDiv.style.display = "none";
        };
    }
    // 信息按钮
    infoButton.onclick = function () {
        let display = infoDiv.style.display;
        if (display == "flex") {
            infoDiv.style.display = "none";
        } else {
            settingDiv.style.display = "none";
            infoDiv.style.display = "flex";
            let title = handler.media.title;
            infoInputs[0].value = title ? title : document.title;
            infoInputs[1].value = handler.media.videoUrl;
            infoInputs[2].value = handler.media.audioUrl;
            infoInputs[3].value = handler.media.subtitleUrl;
            infoInputs[4].value = handler.media.referer;
            infoInputs[5].value = handler.media.origin;
            for (const infoInput of infoInputs) {
                if (infoInput.value) {
                    infoInput.style.cursor = "pointer";
                    infoInput.onclick = function () {
                        this.select();
                        navigator.clipboard.writeText(this.value);
                        toast("已复制到剪贴板");
                    };
                }
            }
        }
    };
    let bestQualityRadios = document.getElementsByName(ID.bestQualityRadio);
    let bilibiliCodecsRadios = document.getElementsByName(
        ID.bilibiliCodecsRadio
    );
    let subtitlePreferRadios = document.getElementsByName(
        ID.subtitlePreferRadio
    );
    // 切换播放器
    function switchPlayer(player) {
        player = PLAYER[player];
        // mpv 和 potplayer 专属
        if (
            player.name == PLAYER.mpv.name ||
            player.name == PLAYER.potplayer.name
        ) {
            switchStatus(softwarePathInput, true);
            softwarePathInput.value = currentConfig[player.name].path;
            if (softwarePathInput.value) {
                switchStatus(downloadButton, true);
            } else {
                switchStatus(downloadButton, false);
            }
        } else {
            switchStatus(softwarePathInput, false);
            switchStatus(downloadButton, false);
        }
        // 代理
        let flag = player.params.proxy ? true : false;
        switchStatus(proxyInput, flag);
        // 音频
        flag = player.params.audioUrl ? true : false;
        for (const radio of bestQualityRadios) {
            switchStatus(radio, flag);
        }
        for (const radio of bilibiliCodecsRadios) {
            switchStatus(radio, flag);
        }
        // 字幕
        flag = player.params.subtitleUrl ? true : false;
        for (const radio of subtitlePreferRadios) {
            switchStatus(radio, flag);
        }
        // 时间
        flag = player.params.startTime ? true : false;
        switchStatus(syncStartTimeSpan, flag);
        switchStatus(syncStartTimeInput, flag);
    }
    // 全屏
    document.addEventListener("fullscreenchange", () => {
        if (document.fullscreenElement) {
            page.isFullScreen = true;
            buttonDiv.style.display = "none";
        } else {
            page.isFullScreen = false;
            if (handler.media.videoUrl) {
                buttonDiv.style.display = "flex";
            }
        }
    });
    // 自定义播放器按钮
    customplayerSettingButton.onclick = function () {
        if (customplayerSettingTable.style.display == "flex") {
            currentConfig.customplayer.params.videoUrl =
                videoUrlParamInput.value;
            currentConfig.customplayer.params.audioUrl =
                audioUrlParamInput.value;
            currentConfig.customplayer.params.subtitleUrl =
                subtitleUrlParamInput.value;
            currentConfig.customplayer.params.title = titleParamInput.value;
            currentConfig.customplayer.params.startTime =
                startTimeParamInput.value;
            currentConfig.customplayer.params.proxy = proxyParamInput.value;
            currentConfig.customplayer.params.referer = refererParamInput.value;
            currentConfig.customplayer.params.origin = originParamInput.value;
            PLAYER.customplayer = currentConfig.customplayer;
            GM_setValue(KEY.config, currentConfig);
            switchPlayer(
                $(`input:radio[name="${ID.playerRadio}"]:checked`).val()
            );
            settingTable.style.display = "flex";
            customplayerSettingTable.style.display = "none";
            customplayerSettingButton.style.backgroundImage =
                ICON_BASE64.customplayer;
            customplayerSettingButton.dataset.tip = "设置自定义播放器";
        } else {
            videoUrlParamInput.value =
                currentConfig.customplayer.params.videoUrl;
            audioUrlParamInput.value =
                currentConfig.customplayer.params.audioUrl;
            subtitleUrlParamInput.value =
                currentConfig.customplayer.params.subtitleUrl;
            titleParamInput.value = currentConfig.customplayer.params.title;
            startTimeParamInput.value =
                currentConfig.customplayer.params.startTime;
            proxyParamInput.value = currentConfig.customplayer.params.proxy;
            refererParamInput.value = currentConfig.customplayer.params.referer;
            originParamInput.value = currentConfig.customplayer.params.origin;
            settingTable.style.display = "none";
            customplayerSettingTable.style.display = "flex";
            customplayerSettingButton.style.backgroundImage = ICON_BASE64.back;
            customplayerSettingButton.dataset.tip = "保存并返回";
        }
    };
}
// 切换元素状态
function switchStatus(element, flag) {
    if (flag) {
        element.readOnly = false;
        element.disabled = false;
        element.classList.remove(CLASS.readOnly);
    } else {
        element.readOnly = true;
        element.disabled = true;
        element.classList.add(CLASS.readOnly);
    }
}
function updatePlayButtonVisibility(currentConfig) {
    let mpv_enabled = currentConfig.mpv.path;
    let mpv_button = document.getElementById(ID.mpvPlayButton);
    let potplayer_enabled = currentConfig.potplayer.path;
    let potplayer_button = document.getElementById(ID.potplayerPlayButton);
    let customplayer_enabled = currentConfig.customplayer.params.videoUrl;
    let customplayerplay_button = document.getElementById(
        ID.customplayerPlayButton
    );
    if (!mpv_enabled && !potplayer_enabled && !customplayer_enabled) {
        mpv_button.style.display = "inline";
        potplayer_button.style.display = "inline";
        customplayerplay_button.style.display = "inline";
        return;
    }
    mpv_button.style.display = mpv_enabled ? "inline" : "none";
    potplayer_button.style.display = potplayer_enabled ? "inline" : "none";
    customplayerplay_button.style.display = customplayer_enabled
        ? "inline"
        : "none";
}
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
// 加载配置
function loadConfig() {
    let oldConifg = GM_getValue(KEY.config);
    currentConfig = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
    currentConfig.mpv.regVersion = "";
    currentConfig.potplayer.regVersion = "";
    if (!oldConifg) {
        GM_setValue(KEY.config, currentConfig);
    } else {
        if (oldConifg.version != DEFAULT_CONFIG.version) {
            for (const key in oldConifg) {
                currentConfig[key] = oldConifg[key];
            }
            if (!currentConfig.mpv.path && currentConfig.mpvPath) {
                currentConfig.mpv.path = currentConfig.mpvPath;
                delete currentConfig["mpvPath"];
            }
            currentConfig.version = DEFAULT_CONFIG.version;
            GM_setValue(KEY.config, currentConfig);
        } else {
            currentConfig = oldConifg;
            GM_setValue(KEY.config, currentConfig);
        }
    }
    PLAYER.customplayer = currentConfig.customplayer;
}
function showPanelForDuration() {
    document.getElementById(ID.infoButton).style.visibility = "visible";
    document.getElementById(ID.settingButton).style.visibility = "visible";
    document.getElementById(ID.mpvPlayButton).style.visibility = "visible";
    document.getElementById(ID.potplayerPlayButton).style.visibility =
        "visible";
    document.getElementById(ID.customplayerPlayButton).style.visibility =
        "visible";
    var hiddenInterval = setInterval(() => {
        if (!isDragging) {
            //拖动时不隐藏
            clearInterval(hiddenInterval);
            document.getElementById(ID.infoButton).style.visibility = "hidden";
            document.getElementById(ID.settingButton).style.visibility =
                "hidden";
            document.getElementById(ID.mpvPlayButton).style.visibility =
                "hidden";
            document.getElementById(ID.potplayerPlayButton).style.visibility =
                "hidden";
            document.getElementById(
                ID.customplayerPlayButton
            ).style.visibility = "hidden";
        }
    }, TIME.showButton);
}
class Media {
    constructor() {
        this.title = "";
        this.videoUrl = "";
        this.audioUrl = "";
        this.subtitleUrl = "";
        this.startTime = "";
        this.referer = "";
        this.origin = "";
        this.proxy = "";
        this.other = "";
    }
    setTitle(title) {
        this.title = title;
    }
    setVideoUrl(videoUrl) {
        if (this.check(videoUrl)) {
            this.videoUrl = videoUrl;
            if (!this.title) {
                this.setTitle(document.title);
            }
            let nxParserIframe = document.getElementById(ID.nxParserIframe);
            if (nxParserIframe) {
                document.body.removeChild(nxParserIframe);
            }
            if (document.getElementById(ID.buttonDiv)) {
                document.getElementById(ID.buttonDiv).style.display = "flex";
                if (
                    currentConfig.playAuto == 1 &&
                    page.url !== "https://www.lckp.top/play-with-mpv/index.html"
                ) {
                    playButtonClick(currentConfig.player);
                }
                showPanelForDuration();
            }
        }
    }
    setAudioUrl(audioUrl) {
        this.audioUrl = audioUrl;
    }
    setSubtitleUrl(subtitleUrl) {
        this.subtitleUrl = subtitleUrl;
    }
    setStartTime(startTime) {
        this.startTime = Math.floor(startTime);
    }
    setReferer(referer) {
        this.referer = referer;
    }
    setOrigin(origin) {
        this.origin = origin;
    }
    setProxy(proxy) {
        this.proxy = proxy;
    }
    setOther(other) {
        this.other = other;
    }
    // 检查视频链接是否有效
    check(videoUrl) {
        if (videoUrl && videoUrl.startsWith("http")) {
            if (
                videoUrl.match(/(\.m3u|\.m3u8)/g) &&
                videoUrl != localStorage.iptvUrl
            ) {
                let m3u8 = "";
                $.ajax({
                    type: "GET",
                    url: videoUrl,
                    async: false,
                    success: function (res) {
                        m3u8 = res;
                    },
                });
                if (m3u8 && m3u8.indexOf("png") != -1) {
                    console.log(
                        "Play-With-MPV：m3u8 链接无法播放：" + videoUrl
                    );
                    return false;
                }
            }
            if (videoUrl.startsWith("https://www.mp4")) {
                return false;
            }
            return true;
        }
        console.log(`Play-With-MPV：链接无效：${videoUrl}`);
        return false;
    }
}
class BaseHandler {
    constructor() {
        loadConfig();
        this.media = new Media();
        this.media.setProxy(currentConfig.proxy);
        for (const key in PLAYER) {
            if (PLAYER[key].name == currentConfig.player) {
                this.player = PLAYER[key];
                break;
            }
        }
        if (window.self == window.top) {
            if (!document.getElementById(ID.buttonDiv)) {
                console.log(INFO);
                appendCSS();
                appendHTML();
                updatePlayButtonVisibility(currentConfig);
                console.log(
                    "加载偏移量：xOffset " +
                        currentConfig.transform.xOffset +
                        " yOffset " +
                        currentConfig.transform.yOffset
                );
                setPosOffset(
                    currentConfig.transform.xOffset,
                    currentConfig.transform.yOffset,
                    document.getElementById(ID.buttonDiv)
                );
                addListener();
            }
            document.getElementById(ID.buttonDiv).style.display = "none";
        }
    }
    initCheck() {
        return window.location.href != page.url;
    }
    async parse() {}
    pause() {
        let videos = document.getElementsByTagName("video");
        if (videos && videos.length > 0) {
            let i = 0;
            while (i < TRY_TIME.maxPause) {
                setTimeout(function () {
                    for (let video of videos) {
                        video.pause();
                    }
                }, TIME.pauseInterval * i);
                i++;
            }
        } else if (this.iframe) {
            this.iframe.postMessage({ method: METHOD.pause }, "*");
        }
    }
    play() {
        let link = "";
        for (let key in this.player.params) {
            if (key == "title") {
                continue;
            }
            if (key == "startTime") {
                if (currentConfig.syncStartTime != 1) {
                    continue;
                } else {
                    let videos = document.getElementsByTagName("video");
                    for (let i = 0; i < videos.length; i++) {
                        if (videos[i].currentTime != 0) {
                            this.media.setStartTime(videos[i].currentTime);
                            break;
                        }
                    }
                }
            }
            let value = this.media[key];
            if (value) {
                let param = this.player.params[key];
                let matchKey = "${" + key + "}";
                while (param.indexOf(matchKey) != -1) {
                    param = param.replace(matchKey, value);
                }
                matchKey = "${E" + key + "}";
                while (param.indexOf(matchKey) != -1) {
                    param = param.replace(matchKey, encodeURIComponent(value));
                }
                matchKey = "${D" + key + "}";
                while (param.indexOf(matchKey) != -1) {
                    param = param.replace(matchKey, decodeURIComponent(value));
                }
                link = link + param;
            }
        }
        if (this.media.title) {
            let maxLength = 1950 - link.length;
            if(maxLength > 2){
                let title = this.media.title;
                let encodeTitle = encodeURIComponent(title);
                while (encodeTitle.length > maxLength) {
                    let index = (maxLength / encodeTitle.length) * title.length;
                    title = title.substring(0, index);
                    encodeTitle = encodeURIComponent(title + "...");
                }
                let param = this.player.params.title;
                param = param.replace("${title}", encodeTitle);
                link = link + param;
            }
        }
        window.open(link, "_self");
    }
    // 监听子页面事件
    addIframeListener() {
        let that = this;
        window.addEventListener(
            "message",
            function (event) {
                that.iframe = event.source;
                if (event.data.method == METHOD.pause) {
                    that.pause();
                } else if (event.data.method == METHOD.report) {
                    that.media.setStartTime(event.data.media.startTime);
                    if (!that.media.videoUrl) {
                        that.media.setVideoUrl(event.data.media.videoUrl);
                    }
                }
            },
            false
        );
    }
    // 监听顶层页面事件
    addTopListener() {
        let that = this;
        window.addEventListener(
            "message",
            function (event) {
                if (event.data.method == METHOD.pause) {
                    that.pause();
                }
            },
            false
        );
        // 定时上报当前视频信息
        setInterval(() => {
            let video = document.getElementsByTagName("video")[0];
            if (video) {
                this.media.setStartTime(video.currentTime);
            }
            window.top.postMessage(
                { method: METHOD.report, media: that.media },
                "*"
            );
        }, TIME.reportInterval);
    }
    // yt-dlp 支持网站解析器
    ytDlpParser() {
        return page.url;
    }
    // video 元素解析器
    videoParser() {
        let videos = document.getElementsByTagName("video");
        for (let video of videos) {
            let url = video.src;
            if (url && url.startsWith("http")) {
                return url;
            }
        }
    }
    // iframe 元素解析器
    iframeParser() {
        let iframes = document.getElementsByTagName("iframe");
        for (let iframe of iframes) {
            let urls = iframe.src.match(VIDEO_URL_REGEX);
            if (urls && urls.length > 0) {
                return urls[0];
            }
        }
    }
    // html 解析器
    htmlParser() {
        let urls = document.body.innerHTML.match(VIDEO_URL_REGEX);
        if (urls && urls.length > 0) {
            return urls[0];
        }
    }
    // script 解析器
    scriptParser() {
        for (let script of document.scripts) {
            let urls = script.innerHTML.match(VIDEO_URL_REGEX);
            if (urls && urls.length > 0) {
                return urls[0];
            }
        }
    }
    // url 解析器
    urlParser() {
        let urls = page.url.match(VIDEO_URL_REGEX);
        if (urls && urls.length > 0) {
            return urls[0];
        }
    }
    // 诺讯解析
    nxParser() {
        handler.addIframeListener();
        let nxParserIframe = document.getElementById(ID.nxParserIframe);
        if (!nxParserIframe) {
            nxParserIframe = document.createElement("iframe");
            nxParserIframe.id = ID.nxParserIframe;
            nxParserIframe.src = `https://yun.nxflv.com/?url=${page.url}`;
            document.body.appendChild(nxParserIframe);
        }
    }
}
// 获取B站视频播放链接
async function getBilibiliPlayUrl(avid, cid) {
    if (handler.player.name == PLAYER.mpv.name) {
        handler.media.setOther(`--script-opts="cid=${cid}"`);
    }
    if (
        !handler.player.params.audioUrl ||
        (await getBilibiliVideoDash(avid, cid)) == -1
    ) {
        getBilibiliVideoDurl(avid, cid);
    }
    if (currentConfig.subtitlePrefer != "off") {
        getBilibiliVideoSubtitle(avid, cid);
        setInterval(() => {
            getBilibiliVideoSubtitle(avid, cid);
        }, 10 * 60 * 1000);
    }
}
// 获取B站 DASH 格式视频
async function getBilibiliVideoDash(avid, cid) {
    let result = 0;
    await $.ajax({
        type: "GET",
        url: `https://api.bilibili.com/x/player/playurl?qn=120&otype=json&fourk=1&fnver=0&fnval=4048&avid=${avid}&cid=${cid}`,
        xhrFields: {
            withCredentials: true,
        },
        async: false,
        success: function (res) {
            if (!res.data) {
                toast(
                    "Play-With-MPV 获取视频失败，如未登录请先登录并刷新页面",
                    TOAST_TYPE.error
                );
                tryTime = TRY_TIME.maxParse;
                return;
            }
            let videoUrl = undefined;
            let audioUrl = undefined;
            let dash = res.data.dash;
            if (!dash) {
                result = -1;
                return;
            }
            let hiRes = dash.flac;
            let dolby = dash.dolby;
            if (hiRes && hiRes.audio) {
                audioUrl = hiRes.audio.baseUrl;
            } else if (dolby && dolby.audio) {
                audioUrl = dolby.audio[0].base_url;
            } else if (dash.audio) {
                audioUrl = dash.audio[0].baseUrl;
            }
            let i = 0;
            while (
                i < dash.video.length &&
                dash.video[i].id >
                    BEST_QUALITY.bilibili[currentConfig.bestQuality]
            ) {
                i++;
            }
            videoUrl = dash.video[i].baseUrl;
            let id = dash.video[i].id;
            while (i < dash.video.length) {
                if (dash.video[i].id != id) {
                    break;
                }
                if (dash.video[i].codecid == currentConfig.bilibiliCodecs) {
                    videoUrl = dash.video[i].baseUrl;
                    break;
                }
                i++;
            }
            handler.media.setAudioUrl(audioUrl);
            handler.media.setVideoUrl(videoUrl);
            result = 1;
        },
    });
    return result;
}
// 获取B站 FLV / MP4 格式视频
function getBilibiliVideoDurl(avid, cid) {
    $.ajax({
        type: "GET",
        url: `https://api.bilibili.com/x/player/playurl?qn=120&otype=json&fourk=1&fnver=0&fnval=128&avid=${avid}&cid=${cid}`,
        xhrFields: {
            withCredentials: true,
        },
        async: false,
        success: function (res) {
            if (!res.data) {
                toast(
                    "Play-With-MPV 获取视频失败，如未登录请先登录并刷新页面",
                    TOAST_TYPE.error
                );
                tryTime = TRY_TIME.maxParse;
                return;
            }
            handler.media.setVideoUrl(res.data.durl[0].url);
        },
    });
}
// 获取B站视频字幕
function getBilibiliVideoSubtitle(avid, cid) {
    $.ajax({
        type: "GET",
        url: `https://api.bilibili.com/x/player/v2?aid=${avid}&cid=${cid}`,
        xhrFields: {
            withCredentials: true,
        },
        async: false,
        success: function (res) {
            if (
                res.code == 0 &&
                res.data.subtitle &&
                res.data.subtitle.subtitles.length > 0
            ) {
                let subtitles = res.data.subtitle.subtitles;
                let url = "https:" + subtitles[0].subtitle_url;
                let lan = subtitles[0].lan;
                for (const subtitle of subtitles) {
                    if (
                        currentConfig.subtitlePrefer.startsWith("zh") &&
                        subtitle.lan.startsWith("zh")
                    ) {
                        url = "https:" + subtitle.subtitle_url;
                        lan = subtitle.lan;
                    }
                    if (subtitle.lan == currentConfig.subtitlePrefer) {
                        url = "https:" + subtitle.subtitle_url;
                        lan = subtitle.lan;
                        break;
                    }
                }
                handler.media.setSubtitleUrl(
                    `https://www.lckp.top/common/bilibili/jsonToSrt/?url=${url}&lan=${lan}`
                );
            }
        },
    });
}
// 获取B站视频 aid 和 cid
function getBilibiliVideoId() {
    let hasInitialState = false;
    try {
        if (__INITIAL_STATE__) {
            hasInitialState = true;
        }
    } catch (error) {
        hasInitialState = false;
    }
    if (!hasInitialState) {
        return undefined;
    }
    let video = undefined;
    if (__INITIAL_STATE__.epInfo) {
        video = __INITIAL_STATE__.epInfo;
    } else if (__INITIAL_STATE__.videoData) {
        video = __INITIAL_STATE__.videoData;
    } else if (__INITIAL_STATE__.videoInfo) {
        video = __INITIAL_STATE__.videoInfo;
    }
    let aid = video.aid;
    let cid = video.cid;
    let p = __INITIAL_STATE__.p;
    if (p && p > 1) {
        cid = __INITIAL_STATE__.cidMap[aid].cids[p];
    }
    let videoId = {
        aid: aid,
        cid: cid,
    };
    return videoId;
}
const BEST_QUALITY = {
    bilibili: {
        unlimited: 127,
        "2160p": 126,
        "1440p": 116,
        "1080p": 116,
        "720p": 74,
        "480p": 32,
    },
    bilibiliLive: {
        unlimited: 4,
        "2160p": 4,
        "1440p": 4,
        "1080p": 4,
        "720p": 3,
        "480p": 2,
    },
    ytdlp: {
        unlimited: "",
        "2160p": "--ytdl-format=bestvideo[height<=?2160]%2Bbestaudio/best",
        "1440p": "--ytdl-format=bestvideo[height<=?1440]%2Bbestaudio/best",
        "1080p": "--ytdl-format=bestvideo[height<=?1080]%2Bbestaudio/best",
        "720p": "--ytdl-format=bestvideo[height<=?720]%2Bbestaudio/best",
        "480p": "--ytdl-format=bestvideo[height<=?480]%2Bbestaudio/best",
    },
};
var websiteList = [
    {
        // ✅ https://www.bilibili.com/bangumi/play/ep508404
        // ✅ https://www.bilibili.com/bangumi/play/ep319063
        name: "B站影视",
        home: ["https://www.bilibili.com"],
        regex: /^https:\/\/www\.bilibili\.com\/bangumi\/play\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.media.setReferer("https://www.bilibili.com");
            }
            async parse() {
                // 直接从数据中获取 aid 和 cid
                // let videoId = getBilibiliVideoId();
                // if (videoId && videoId.aid && videoId.cid) {
                //     getBilibiliPlayUrl(videoId.aid, videoId.cid);
                //     return;
                // }

                // 从元素提取 epid 请求接口获取 aid 和 cid
                let epid = page.url.match(/ep(\d+)/);
                if (epid && epid[1]) {
                    epid = epid[1];
                } else {
                    let epidElement = document.getElementsByClassName(
                        "ep-item cursor visited"
                    )[0];
                    if (!epidElement) {
                        epidElement =
                            document.getElementsByClassName(
                                "ep-item cursor"
                            )[0];
                    }
                    if (epidElement) {
                        epid = epidElement
                            .getElementsByTagName("a")[0]
                            .href.match(/ep(\d+)/)[1];
                    } else {
                        epidElement = document.getElementsByClassName(
                            "squirtle-pagelist-select-item active squirtle-blink"
                        )[0];
                        if (epidElement) {
                            epid = epidElement.dataset.value;
                        }
                    }
                }
                if (!epid) {
                    return;
                }
                $.ajax({
                    type: "GET",
                    url: `https://api.bilibili.com/pgc/view/web/season?ep_id=${epid}`,
                    xhrFields: {
                        withCredentials: true,
                    },
                    async: false,
                    success: function (res) {
                        let currentEpisode;
                        let section = res.result.section;
                        if (!section) {
                            section = new Array();
                        }
                        section.push({ episodes: res.result.episodes });
                        for (let i = section.length - 1; i >= 0; i--) {
                            let episodes = section[i].episodes;
                            for (const episode of episodes) {
                                if (episode.id == epid) {
                                    currentEpisode = episode;
                                    break;
                                }
                            }
                            if (currentEpisode) {
                                break;
                            }
                        }
                        getBilibiliPlayUrl(
                            currentEpisode.aid,
                            currentEpisode.cid
                        );
                    },
                });
            }
        },
    },
    {
        // ✅ https://www.bilibili.com/video/BV1Hd4y1k7Vb
        // ✅ https://www.bilibili.com/video/av2
        // ✅ https://www.bilibili.com/video/BV17Z4y117Qm
        // ✅ https://www.bilibili.com/list/ml1806211634?oid=822115390&bvid=BV1Fg4y1p7Qe
        name: "B站投稿",
        regex: /^https:\/\/www\.bilibili\.com\/(video\/|list.*)(BV|av).*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.media.setReferer("https://www.bilibili.com");
            }
            initCheck() {
                if (super.initCheck()) {
                    let newPageUrl = window.location.href;
                    let oldPageUrl = page.url;
                    let regex = /(&|\?)vd_source=\w+/;
                    if (regex.test(newPageUrl.replace(oldPageUrl, ""))) {
                        page.url = newPageUrl;
                        return false;
                    }
                    return true;
                }
                return false;
            }
            async parse() {
                // 直接从数据中获取 aid 和 cid
                // let videoId = getBilibiliVideoId();
                // if (videoId && videoId.aid && videoId.cid) {
                //     getBilibiliPlayUrl(videoId.aid, videoId.cid);
                //     return;
                // }

                // 通过 bvid/avid 请求接口获取 aid 和 cid
                let param = undefined;
                let bvid = page.url.match(/BV([0-9a-zA-Z]+)/);
                if (bvid && bvid[1]) {
                    param = `bvid=${bvid[1]}`;
                } else {
                    let avid = page.url.match(/av([0-9]+)/);
                    if (avid && avid[1]) {
                        param = `aid=${avid[1]}`;
                    }
                }
                if (!param) {
                    return;
                }
                $.ajax({
                    type: "GET",
                    url: `https://api.bilibili.com/x/web-interface/view?${param}`,
                    xhrFields: {
                        withCredentials: true,
                    },
                    async: false,
                    success: function (res) {
                        let aid = res.data.aid;
                        let cid = res.data.cid;
                        let index = page.url.indexOf("?p=");
                        if (index != -1 && res.data.pages.length > 1) {
                            let p = page.url.substring(index + 3);
                            let endIndex = p.indexOf("&");
                            if (endIndex != -1) {
                                p = p.substring(0, endIndex);
                            }
                            cid = res.data.pages[p - 1].cid;
                        }
                        getBilibiliPlayUrl(aid, cid);
                    },
                });
            }
        },
    },
    {
        // ✅ https://www.bilibili.com/festival/2023bnj?bvid=BV17G4y1X7vQ
        name: "B站节日",
        regex: /^https:\/\/www\.bilibili\.com\/festival\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.media.setReferer("https://www.bilibili.com");
            }
            initCheck() {
                if (super.initCheck()) {
                    return true;
                }
                let oldvideoId = this.videoId;
                let newvideoId = getBilibiliVideoId();
                if (oldvideoId && oldvideoId.cid != newvideoId.cid) {
                    return true;
                }
                return false;
            }
            async parse() {
                let videoId = getBilibiliVideoId();
                if (videoId && videoId.aid && videoId.cid) {
                    this.videoId = videoId;
                    getBilibiliPlayUrl(videoId.aid, videoId.cid);
                    return;
                } else {
                    toast(
                        "Play-With-MPV 读取视频数据失败，请尝试清理B站缓存后刷新重试",
                        TOAST_TYPE.error,
                        5000
                    );
                    tryTime = TRY_TIME.maxParse;
                }
            }
        },
    },
    {
        // ✅ https://live.bilibili.com/7777
        name: "B站直播",
        home: ["https://live.bilibili.com"],
        regex: /^https:\/\/live\.bilibili\.com\/\d+.*/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                let iframes = document.getElementsByTagName("iframe");
                let roomid = undefined;
                for (let iframe of iframes) {
                    let roomids = iframe.src.match(
                        /^https:\/\/live\.bilibili\.com.*(roomid=\d+|blanc\/\d+).*/
                    );
                    if (roomids && roomids[1]) {
                        roomid = roomids[1].match(/\d+/)[0];
                        break;
                    }
                }
                if (!roomid) {
                    console.log("Play-With-MPV：找不到 roomid：" + roomid);
                    return;
                }

                let that = this;
                $.ajax({
                    type: "GET",
                    url: `https://api.live.bilibili.com/room/v1/Room/playUrl?quality=${
                        BEST_QUALITY.bilibiliLive[currentConfig.bestQuality]
                    }&cid=${roomid}`,
                    async: false,
                    xhrFields: {
                        withCredentials: true,
                    },
                    success: function (res) {
                        that.media.setVideoUrl(res.data.durl[0].url);
                    },
                });
            }
        },
    },
    {
        // ✅ https://www.ixigua.com/
        name: "西瓜视频",
        home: ["https://www.ixigua.com"],
        regex: /^https:\/\/www\.ixigua\.com\/\d.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.media.setReferer("https://www.ixigua.com/");
            }
            async parse() {
                let that = this;
                $.ajax({
                    type: "GET",
                    url: page.url,
                    async: false,
                    success: function (res) {
                        try {
                            let _SSR_HYDRATED_DATA = new Function(
                                "return " +
                                    res.match(
                                        /<script id="SSR_HYDRATED_DATA"[^<]*window._SSR_HYDRATED_DATA=({[^<]*})[^<]*<\/script>/
                                    )[1]
                            )();
                            let packerData =
                                _SSR_HYDRATED_DATA.anyVideo.gidInformation
                                    .packerData;
                            let main_url = undefined;
                            if (packerData.video) {
                                let videoList =
                                    packerData.video.videoResource.normal
                                        .video_list;
                                if (videoList) {
                                    let video = undefined;
                                    for (const key in videoList) {
                                        if (
                                            !video ||
                                            videoList[key].vheight >
                                                video.vheight
                                        ) {
                                            video = videoList[key];
                                        }
                                    }
                                    main_url = video.main_url;
                                }
                                that.media.setVideoUrl(
                                    window
                                        .atob(main_url)
                                        .replace("http://", "https://")
                                );
                                return;
                            }
                        } catch (error) {
                            console.error("解析出错：" + error);
                        }
                        that.nxParser();
                    },
                });
            }
        },
    },
    {
        // ✅ https://yun.nxflv.com/?url=https://www.ixigua.com/7186534626612118071
        name: "诺讯解析",
        regex: /^https:\/\/yun\.nxflv\.com\/\?url=.+/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                let url = this.videoParser();
                if (url.startsWith("blob")) {
                    for (
                        let index = 0;
                        index < sessionStorage.key.length;
                        index++
                    ) {
                        url = sessionStorage.key(index);
                        url = url.match(/http[^#]*/g);
                        if (url && url.length > 0) {
                            url = url[0];
                        }
                    }
                }
                this.media.setVideoUrl(url);
            }
        },
    },
    {
        // ✅ https://ddys.art/bleach-thousand-year-blood-war
        name: "低端影视",
        home: ["https://ddys.art", "https://ddys.pro"],
        regex: /^https:\/\/(ddys\.art|ddys\.pro)\/.*/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                let video = document.getElementsByTagName("video")[0];
                if (video.paused) {
                    document
                        .getElementsByClassName("vjs-big-play-button")[0]
                        .click();
                }
                let url = this.videoParser();
                if (url) {
                    let index = url.indexOf("?");
                    if (index != -1) {
                        url =
                            url.substring(0, index + 1) +
                            encodeURIComponent(url.substring(index + 1));
                    }
                    this.media.setVideoUrl(url);
                    let playing = document.getElementsByClassName(
                        "wp-playlist-playing"
                    )[0];
                    if (playing) {
                        let episode = playing.textContent.replace(
                            /(\n|\t|\d\.)/g,
                            ""
                        );
                        if (episode != " 全") {
                            this.media.title =
                                document.getElementsByClassName("post-title")[0]
                                    .textContent +
                                episode +
                                " - 低端影视";
                        }
                    }
                }
            }
        },
    },
    {
        // ✅ https://www.libvio.pro/play/714890512-1-2.html
        name: "LIBVIO",
        home: [
            "https://www.libvio.cc",
            "https://libvio.fun",
            "https://libvio.me",
            "https://www.libvio.me",
        ],
        regex: /^https?:\/\/.*\.libvio\..*\/play.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "LIBVIO播放器",
        regex: /^https:\/\/(.*\.chinaeast2\.cloudapp\.chinacloudapi\.cn|.*\.cfnode1\.xyz)(:\d+)?\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
                let that = this;
                this.currentUrl = "";
                //拦截请求以更新Url
                const originOpen = XMLHttpRequest.prototype.open;
                XMLHttpRequest.prototype.open = function (
                    method,
                    url,
                    async,
                    user,
                    password
                ) {
                    originOpen.apply(this, arguments);
                    if (url.match(VIDEO_URL_REGEX)) {
                        that.currentUrl = encodeURIComponent(url);
                    }
                };
            }
            async parse() {
                if (this.currentUrl) {
                    this.media.setVideoUrl(this.currentUrl);
                } else {
                    let url = urls;
                    let index = url.indexOf("?");
                    if (index != -1) {
                        url =
                            url.substring(0, index + 1) +
                            encodeURIComponent(url.substring(index + 1));
                    }
                    this.media.setVideoUrl(url);
                }
            }
        },
    },
    {
        // ✅ https://www.nivod.tv/UXEwMmLqnUjHG5e4MwmlvmVnWiAJ9rIQ-RofV7wPhhed3uoi50mYsftLPq4mYyIhB-720-0-0-play.html?x=1
        // ✅ https://www.nivod4.tv/YeClpGXOt58F3QmAp9bx5CnaBrO4txhU-27-0-0-0-play.html?sc=6bb1aa9619ece1f6a00124a66a6fb8b6
        name: "泥视频",
        home: ["https://www.nivod.tv"],
        regex: /^https:\/\/www\.nivod.*\.tv\/.*play\.html?.*/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                this.media.setVideoUrl(__dp.options.video.url);
                this.media.setTitle(document.title);
            }
        },
    },
    {
        // ✅ https://www.pkmkv.com/py/268677-2-11.html
        name: "片库",
        home: ["https://www.pkmkv.com"],
        regex: /^https:\/\/www\.pkmkv\.com\/py\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
            async parse() {
                this.media.setVideoUrl(player_aaaa.url);
            }
        },
    },
    {
        name: "片库播放器",
        regex: /^https:\/\/www\.pkmkv\.com\/addons\/dplayer\/\?url=.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
        },
    },
    {
        // ✅ https://tgbook.coolkv.com/play/1048-0-4.html
        name: "韩剧看看",
        regex: /^https:\/\/tgbook\.coolkv\.com\/play\/.*\.html/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
                this.media.setReferer("https://tgbook.coolkv.com/");
            }
        },
    },
    {
        // https://tgbook.coolkv.com/js/player/dm.html
        name: "韩剧看看播放器外层",
        regex: /^https:\/\/tgbook\.coolkv\.com\/js\/player\/dm\.html/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
                this.addIframeListener();
            }
        },
    },
    {
        // https://tgbook.coolkv.com/js/player/dmplayer/player/index.php?url=https://99vv.yandex.com/file/bz2022/2021/yyyx/yyyx05/playlist.m3u8&next=/play/1048-0-5.html&vid=1048-0-4&nextdz=https://99vv.yandex.com/file/bz2022/2021/yyyx/yyyx06/playlist.m3u8
        name: "韩剧看看播放器",
        regex: /^https:\/\/tgbook\.coolkv\.com\/js\/player\/dmplayer\/player\/index\.php\?url=.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                if (
                    config.url.indexOf(".m3u8") > 0 ||
                    config.url.indexOf(".mp4") > 0 ||
                    config.url.indexOf(".flv") > 0
                ) {
                    this.media.setVideoUrl(config.url);
                }
            }
        },
    },
    {
        // ✅ https://www.btnull.org/py/BBnLd_9.html?167094
        name: "无名小站",
        home: [
            "https://www.btnull.org",
            "https://www.btnull.to",
            "https://www.btnull.nu",
            "https://www.btnull.in",
        ],
        regex: /^https:\/\/(www.btnull.org|www.btnull.to|www.btnull.nu|www.btnull.in)\/py\/.*/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                this.media.setVideoUrl(this.htmlParser());
            }
        },
    },
    {
        // ✅ https://www.816dm.com/play/6792-1-91.html
        // ✅ http://www.ntdm8.com/play/4973-1-1.html
        name: "樱花动漫网",
        home: ["https://www.816dm.com", "http://www.ntdm8.com"],
        regex: /^https?:\/\/www\.(\d+dm|ntdm8)\.com\/play\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "樱花动漫网播放器",
        regex: /^https:\/\/danmu\.yhdmjx\.com\/.*php\?url=.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(this.videoParser());
            }
        },
    },
    {
        // ✅ https://dick.xfani.com/watch/582/1/11.html
        name: "稀饭动漫",
        home: ["https://dick.xfani.com"],
        regex: /^https:\/\/dick\.xfani\.com\/watch\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "稀饭动漫播放器",
        regex: /(^https:\/\/dick\.xfani\.com\/addons\/dp\/player\/.*|^https:\/\/m3\.moedot\.net\/muiplayer\/\?url=.*)/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                if (
                    config.url.indexOf(".m3u8") > 0 ||
                    config.url.indexOf(".mp4") > 0 ||
                    config.url.indexOf(".flv") > 0
                ) {
                    this.media.setVideoUrl(config.url);
                } else {
                    let that = this;
                    $.ajax({
                        type: "POST",
                        url: "api_currentConfig.php",
                        data: {
                            url: config.url,
                            time: config.time,
                            key: config.key,
                            title: config.title,
                        },
                        async: false,
                        success: function (res) {
                            if (res.code == "200") {
                                that.media.setVideoUrl(res.url);
                            }
                        },
                    });
                }
            }
        },
    },
    {
        name: "稀饭动漫播放器",
        regex: /^https:\/\/player\.moedot\.net\/player\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(this.urlParser());
            }
        },
    },
    {
        // ✅ https://www.mgnacg.com/bangumi/426-6-12
        name: "橘子动漫",
        home: ["https://www.mgnacg.com"],
        regex: /^https:\/\/www\.mgnacg\.com\/bangumi\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
                this.media.setReferer("https://play.mknacg.top:8585/");
            }
        },
    },
    {
        name: "橘子动漫播放器",
        regex: /^https:\/\/play\.mknacg\.top:8585\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(art.option.url);
            }
        },
    },
    {
        // ✅ https://www.omofun.top/index.php/vod/play/id/17295/sid/2/nid/7.html
        name: "OmoFun",
        home: ["https://www.omofun.top"],
        regex: /^https:\/\/www\.omofun\.top\/index.php\/vod\/play\/id\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "OmoFun播放器",
        regex: /^https:\/\/.*\.omofun\.top\/(player\/|)\?url=.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(this.urlParser());
            }
        },
    },
    {
        // ✅ https://spdcat.net/vodplay/135443-1-23
        name: "迅猫动漫",
        home: ["https://spdcat.net"],
        regex: /^https:\/\/spdcat\.net\/vodplay\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "迅猫动漫播放器",
        regex: /^https:\/\/spdcat\.net\/addons\/dp\/player\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(config.url);
                setTimeout(() => {
                    let conplay =
                        document.getElementsByClassName("conplay-jump")[0];
                    if (conplay) {
                        conplay.click();
                    }
                }, 1000);
            }
        },
    },
    {
        // ✅ http://www.dm88.me/player/8480-0-11.html
        name: "樱花动漫",
        home: ["http://www.dm88.me"],
        regex: /^http:\/\/www\.dm88\.me\/player\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "樱花动漫播放器",
        regex: /^https:\/\/jianghu\.live2008\.com\/.*\?url=.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(url);
            }
        },
    },
    {
        // ✅ https://www.kk151.com/play/15257-2-11.html
        name: "动漫之家",
        home: ["https://www.kk151.com"],
        regex: /^https:\/\/www\.kk151\.com\/play\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "动漫之家播放器",
        regex: /^https:\/\/(www\.ikdmjx\.com\/|jx\.wolongzywcdn\.com:65\/m3u8\.php|hls\.kuaibofang\.com\/|jx\.jxbdzyw\.com\/m3u8\/|www\.m3u8\.tv\.cdn\.8old\.cn\/m3u8tv1127\/api\.php|jx\.wujinkk\.com\/dplayer\/|jx\.m3u8\.tv\/jiexi\/)\?url=.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(this.urlParser());
            }
        },
    },
    {
        // ✅ https://hdzyk.com/?m=vod-play-id-27537-src-2-num-11.html
        // ✅ https://1080zyk2.com/?m=vod-play-id-27537-src-2-num-11.html
        name: "优质资源库",
        home: [
            "https://hdzyk.com",
            "https://1080zyk1.com",
            "https://1080zyk2.com",
            "https://1080zyk3.com",
            "https://1080zyk4.com",
            "https://1080zyk5.com",
        ],
        regex: /^https:\/\/(hdzyk\.com|1080zyk[1-5]\.com)\/\?m=.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "优质资源库播放器",
        regex: /^https:\/\/vip\.zykbf\.com\/\?url=.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(this.urlParser());
            }
        },
    },
    {
        name: "优质资源库播放器",
        regex: /^https:\/\/.*\.(yzzy-tv1|yzzy-tv-cdn)\.com\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl("https://" + location.host + main);
            }
        },
    },
    {
        // ✅ https://www.bdys10.com/play/22729-8.htm
        name: "哔嘀影视",
        home: ["https://www.bdys10.com"],
        regex: /^https:\/\/www\.bdys10\.com\/.*play\/.*/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                this.media.setVideoUrl(this.videoParser());
                if (!this.media.videoUrl) {
                    this.media.setVideoUrl(this.htmlParser());
                    if (!this.media.videoUrl) {
                        this.media.setVideoUrl(this.scriptParser());
                    }
                }
            }
        },
    },
    {
        // ✅ https://www.dora-family.com/Resource:TV
        name: "哆啦A梦新番",
        home: ["https://www.dora-family.com/Resource:TV"],
        regex: /^https:\/\/www\.dora-family\.com\/Resource:TV/g,
        handler: class Handler extends BaseHandler {
            initCheck() {
                if (super.initCheck()) {
                    return true;
                }
                let oldVideoUrl = this.media.videoUrl;
                let newVideoUrl = this.videoParser();
                if (oldVideoUrl && oldVideoUrl != newVideoUrl) {
                    return true;
                }
                return false;
            }
            async parse() {
                this.media.setVideoUrl(this.videoParser());
            }
        },
    },
    {
        // ✅ https://mypikpak.com/drive/all
        name: "PikPak",
        home: ["https://mypikpak.com/drive/all"],
        regex: /^https:\/\/mypikpak\.com\/drive\/.*/g,
        handler: class Handler extends BaseHandler {
            initCheck() {
                if (super.initCheck()) {
                    return true;
                }
                let oldVideoUrl = this.media.videoUrl;
                let newVideoUrl = this.videoParser();
                if (oldVideoUrl && oldVideoUrl != newVideoUrl) {
                    return true;
                }
                return false;
            }
            async parse() {
                while (document.getElementsByTagName("video").length == 0) {
                    await sleep(1000);
                }
                this.media.setVideoUrl(this.videoParser());
                this.media.setTitle(
                    document.getElementsByClassName("player-title")[0]
                        .textContent
                );
            }
        },
    },
    {
        // ✅ https://www.olehdtv.com/player/vod/1/43671/1
        name: "欧乐影院",
        home: ["https://www.olehdtv.com/"],
        regex: /^https:\/\/www\.olehdtv\.com\/player\/vod\/\d+\/\d+\/\d+/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                let ids = page.url.match(
                    /^https:\/\/www\.olehdtv\.com\/player\/vod\/(\d+)\/(\d+)\/(\d+)/
                );
                let id = ids[2];
                let index = ids[3];
                let t = Date.parse(new Date()) / 1e3,
                    r = t % 20;
                let _vv = MD5(t - r + "new.olelive.com");
                let that = this;
                $.ajax({
                    type: "GET",
                    url: `https://api.olelive.com/v1/pub/vod/detail?id=${id}&play=true&_vv=${_vv}`,
                    async: false,
                    success: function (res) {
                        if (res.code == 0) {
                            that.media.setVideoUrl(
                                res.data.urls[index - 1].url
                            );
                        }
                    },
                });
            }
            play() {
                this.media.setTitle(document.title);
                super.play();
            }
        },
    },
    {
        // ✅ https://www.olehdtv.com/player/live/tv/CCTV5HD/49
        name: "欧乐影院直播",
        home: ["https://www.olehdtv.com/"],
        regex: /^https:\/\/www\.olehdtv\.com\/player\/live\/tv\/[^/]+\/\d+/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                let ids = page.url.match(
                    /^https:\/\/www\.olehdtv\.com\/player\/live\/tv\/([^/]+)\/(\d+)/
                );
                let id = ids[2];
                let streamId = ids[1];
                let today = new Date();
                let year = today.getFullYear();
                let month = today.getMonth() + 1;
                if (month < 10) {
                    month = "0" + month;
                }
                let day = today.getDate();
                if (day < 10) {
                    day = "0" + day;
                }
                let date = year + "-" + month + "-" + day;
                let t = Date.parse(today) / 1e3,
                    r = t % 20;
                let _vv = MD5(t - r + "new.olelive.com");
                let that = this;
                $.ajax({
                    type: "GET",
                    url: `https://api.olelive.com/v1/pub/live/info?date=${date}&streamId=${streamId}&type=tv&id=${id}&_vv=${_vv}`,
                    async: false,
                    success: function (res) {
                        if (res.code == 0) {
                            that.media.setVideoUrl(
                                res.data.detail.hls.replace("_360", "")
                            );
                        }
                    },
                });
            }
            play() {
                this.media.setTitle(document.title);
                super.play();
            }
        },
    },
    {
        // ✅ https://tkznp.com/vodplay/337990-1-2.html
        name: "天空影视",
        home: [
            "https://tkznp.com/",
            "https://www.tkznp1.com/",
            "https://www.tkznp2.com/",
            "https://www.tkznp3.com/",
            "https://www.tkznp4.com/",
            "https://www.tkznp5.com/",
            "https://www.tkznp6.com/",
        ],
        regex: /^https?:\/\/(|www\.)tkznp(|1|2|3|4|5|6)\.com\/vodplay\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "天空影视播放器",
        regex: /^https?:\/\/vip\.ckllk\.com\/\?url=.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(config.url);
                this.media.setTitle(config.title);
            }
        },
    },
    {
        // ✅ https://www.anfuns.cc/play/1572-1-1.html
        name: "AnFuns",
        home: ["https://www.anfuns.cc"],
        regex: /^https:\/\/www\.anfuns\.cc\/play\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "AnFuns播放器",
        regex: /^https:\/\/www\.anfuns\.cc\/vapi\/(A0EPlayer|eden)\/\?url=.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                let url = config.url;
                if (url) {
                    if (
                        !url.startsWith("https://fata.peizq.online/cache/") &&
                        !url.startsWith("https://media-oss.anfuns.cn/m3u8/")
                    ) {
                        this.media.setVideoUrl(config.url);
                    } else {
                        tryTime = TRY_TIME.maxParse;
                    }
                }
            }
        },
    },
    {
        // ✅ https://www.youtube.com/watch?v=IkGuTYaTsLo
        name: "YouTube",
        home: ["https://www.youtube.com"],
        regex: /^https:\/\/www\.youtube\.com\/(watch|playlist)\?.*/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                this.media.setOther(
                    BEST_QUALITY.ytdlp[currentConfig.bestQuality]
                );
                this.media.setVideoUrl(this.ytDlpParser());
                this.media.setTitle("");
            }
        },
    },
    {
        // ✅ https://odysee.com/@jjlin:8/%E6%9E%97%E4%BF%8A%E5%82%91-jj-lin%E3%80%8Ajj20%E4%B8%96%E7%95%8C%E5%B7%A1%E8%BF%B4%E6%BC%94%E5%94%B1%E6%9C%83%E3%80%8B-2:8
        name: "Odysee",
        home: ["https://odysee.com"],
        regex: /^https:\/\/odysee\.com\/[^$].+/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                this.media.setOther(
                    BEST_QUALITY.ytdlp[currentConfig.bestQuality]
                );
                this.media.setVideoUrl(this.ytDlpParser());
                this.media.setTitle("");
            }
        },
    },
    {
        // ✅ https://rumble.com/v2mfr78-valheim-viking-survival-w-chaos-tricks-you-can-affect-my-game-chat-opinions.html
        name: "Rumble",
        home: ["https://rumble.com"],
        regex: /^https:\/\/rumble\.com\/v.+\.html/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                this.media.setOther(
                    BEST_QUALITY.ytdlp[currentConfig.bestQuality]
                );
                this.media.setVideoUrl(this.ytDlpParser());
                this.media.setTitle("");
            }
        },
    },
    {
        // ✅ https://www.bitchute.com/video/NoodZjmfKHXS/
        name: "BitChute",
        home: ["https://www.bitchute.com"],
        regex: /^https:\/\/www\.bitchute\.com\/video\/.+/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                this.media.setOther(
                    BEST_QUALITY.ytdlp[currentConfig.bestQuality]
                );
                this.media.setVideoUrl(this.ytDlpParser());
                this.media.setTitle("");
            }
        },
    },
    {
        // ✅ https://ani.gamer.com.tw/animeVideo.php?sn=32227
        name: "巴哈姆特",
        home: ["https://ani.gamer.com.tw"],
        regex: /^https:\/\/ani\.gamer\.com\.tw\/animeVideo.php\?sn=.*/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                this.media.setOrigin("https://ani.gamer.com.tw");
                let index = page.url.indexOf("sn=") + 3;
                if (index == -1) {
                    return;
                }
                let sn = page.url.substring(index);
                index = sn.indexOf("&");
                if (index != -1) {
                    sn = sn.substring(0, index);
                }
                let device = localStorage.ANIME_deviceid;
                let that = this;
                let res;
                $.ajax({
                    type: "GET",
                    url: `https://ani.gamer.com.tw/ajax/m3u8.php?sn=${sn}&device=${device}`,
                    async: false,
                    xhrFields: {
                        withCredentials: true,
                    },
                    success: function (json) {
                        res = JSON.parse(json);
                    },
                });
                if (res.error) {
                    if (res.error.code == 1015) {
                        let oldDuration = document.getElementsByClassName(
                            "vjs-duration-display"
                        )[0].innerHTML;
                        let newDuration = oldDuration;
                        while (oldDuration == newDuration) {
                            await sleep(1000);
                            newDuration = document.getElementsByClassName(
                                "vjs-duration-display"
                            )[0].innerHTML;
                        }
                        tryTime = 0;
                    }
                } else {
                    that.media.setProxy(currentConfig.proxy);
                    that.media.setVideoUrl(res.src);
                }
            }
        },
    },
    {
        // ✅ https://hanime1.me/watch?v=26262
        name: "Hanime1.me",
        home: ["https://hanime1.me"],
        regex: /^https:\/\/hanime1\.me\/watch\?v=.*/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                let url = this.videoParser();
                if (!url) {
                    url = this.iframeParser();
                }
                if (!url) {
                    url = page.url;
                }
                this.media.setVideoUrl(url);
            }
        },
    },
    {
        // ✅ https://jable.tv/videos/fsdss-484/
        name: "Jable.TV",
        home: ["https://jable.tv"],
        regex: /^https:\/\/jable\.tv\/videos\/.*/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                let url = hls.url;
                url = url ? url : hlsUrl;
                url = url ? url : page.url;
                this.media.setVideoUrl(url);
            }
        },
    },
    {
        // ✅ https://ok.ru/video/2035990725937
        name: "OK",
        home: ["https://ok.ru/video"],
        regex: /^https:\/\/ok\.ru\/video\/\d+/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                this.media.setOther(
                    BEST_QUALITY.ytdlp[currentConfig.bestQuality]
                );
                this.media.setVideoUrl(this.ytDlpParser());
                this.media.setTitle("");
            }
        },
    },
    {
        // ✅ https://tver.jp/episodes/epsta1xs0z
        name: "TVer",
        home: ["https://tver.jp"],
        regex: /^https:\/\/tver\.jp\/episodes\/\w+/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                this.media.setOther(
                    BEST_QUALITY.ytdlp[currentConfig.bestQuality]
                );
                this.media.setVideoUrl(this.ytDlpParser());
                this.media.setTitle("");
            }
        },
    },
    {
        // ✅ https://www.lckp.top/play-with-mpv/index.html
        name: "电视直播",
        home: ["https://www.lckp.top/play-with-mpv/index.html"],
        regex: /^https?:\/\/(www.lckp.top\/play-with-mpv|127.0.0.1:5502\/web\/tampermonkey\/Play-With-MPV)\/index.html/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                this.media.setVideoUrl(localStorage.iptvUrl);
                localStorage.player = JSON.stringify(this.player);
                this.media.setTitle("");
            }
        },
    },
    {
        // ✅ https://www.douyin.com/
        name: "抖音",
        home: ["https://www.douyin.com/"],
        regex: /^https?:\/\/www\.douyin\.com\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.index = 0;
            }
            initCheck() {
                if (super.initCheck()) {
                    return true;
                }
                let oldVideoUrl = this.media.videoUrl;
                let newVideoUrl = this.videoParser();
                if (oldVideoUrl && oldVideoUrl != newVideoUrl) {
                    return true;
                }
                return false;
            }
            async parse() {
                this.media.setVideoUrl(this.videoParser());
            }
            videoParser() {
                let videos = document.getElementsByTagName("video");
                if (videos && videos.length > 0) {
                    this.index = videos.length > 2 ? 1 : 0;
                }
                let url = document
                    .getElementsByTagName("video")
                    [this.index].getElementsByTagName("source")[0].src;
                if (url) {
                    return url;
                }
            }
        },
    },
    {
        // ✅ https://www.mfan.tv/play/Na666U/1/1/
        // ✅ https://www.mengfan.tv/play/kx666U/1/3/
        name: "萌番",
        home: ["https://www.mengfan.tv", "https://www.mfan.tv"],
        regex: /^https:\/\/www\.(mengfan|mfan)\.tv\/play\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "萌番播放器",
        regex: /^https:\/\/video1\.beijcloud\.com\/player\/\?url=.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(this.scriptParser());
            }
        },
    },
    {
        // ✅ https://www.haitu.tv/vod/play/id/47100/sid/1/nid/4.html
        name: "海兔影院",
        home: ["https://www.haitu.tv"],
        regex: /^https:\/\/www\.haitu\.tv\/vod\/play\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "海兔影院播放器",
        regex: /^https:\/\/www\.haitu\.tv\/static\/dmku\/player\/index\.php/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(config.url);
            }
        },
    },
    {
        // ✅ https://www.icourse163.org/learn/ZJU-200001?tid=1470096517#/learn/content?type=detail&id=1254347726&cid=1285600901
        name: "中国大学MOOC",
        regex: /^https:\/\/www\.icourse163\.org\/learn\/.*\/learn\/content\?type=detail.*/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                let res = this.htmlParser();
                if (!res) return;
                this.media.setVideoUrl(res);
                this.media.setTitle(
                    document.querySelector(".current > .unit-name").innerText
                );
            }
        },
    },
    {
        // ✅ https://www.iole.tv/vodplay/23711-1-1.html
        name: "ioleTV",
        regex: /^https:\/\/www\.iole\.tv\/vodplay\/.+/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "ioleTV播放器",
        regex: /^https:\/\/www\.iole\.tv\/static\/player\/.*\.html/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(parent.MacPlayer.PlayUrl);
            }
        },
    },
    {
        // ✅ https://www.zhihu.com/zvideo/1650574385558937600
        name: "知乎视频",
        regex: /^https:\/\/www\.zhihu\.com\/zvideo\/.+/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                let that = this;
                this.currentUrl = "";
                //拦截请求以更新Url
                const originOpen = XMLHttpRequest.prototype.open;
                XMLHttpRequest.prototype.open = function (
                    method,
                    url,
                    async,
                    user,
                    password
                ) {
                    originOpen.apply(this, arguments);
                    if (url.match(VIDEO_URL_REGEX)) {
                        that.currentUrl = url;
                    }
                };
            }
            async parse() {
                this.media.setVideoUrl(this.currentUrl);
            }
        },
    },
    {
        // ✅ https://www.tucao.cam/play/h4092670/#1
        name: "吐槽弹幕网",
        regex: /^https:\/\/www\.tucao\.cam\/play\/.*/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                this.media.setVideoUrl(this.videoParser());
            }
        },
    },
    {
        // ✅ http://www.susudm8.com/acg/69815/3.html
        name: "速速电影院",
        regex: /^https?:\/\/(www\.susudm8\.com|susudyy\.com|buding3\.com|buding6\.com)\/.+\.html/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "速速电影院播放器",
        regex: /^https?:\/\/(v2\.shenjw\.com:\d+|u88\.xigua88ok\.com:\d+)\/wap\.php\?url=.+/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(this.videoParser());
            }
        },
    },
    {
        name: "速速电影院播放器",
        regex: /^https?:\/\/test3\.gqyy8\.com:\d+\/f\/aliplayer\.php\?url=.+/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(this.scriptParser());
            }
        },
    },
    {
        // ✅ https://v.mksec.cn/index.php/vod/play/id/165438/sid/2/nid/1.html
        name: "小见子的视频站",
        regex: /^https?:\/\/v\.mksec\.cn\/index\.php\/vod\/play\/.+\.html/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
            async parse() {
                this.media.setVideoUrl(player_aaaa.url);
            }
        },
    },
    {
        name: "小见子的视频站播放器",
        regex: /^https?:\/\/v\.mksec\.cn\/static\/player\/dplayer\.html/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                tryTime = TRY_TIME.maxParse;
            }
        },
    },
    {
        // ✅ https://jh642t.dshryadqp.com/index.php/vod/play/id/51434/sid/1/nid/1.html
        name: "大师兄电影网",
        regex: /^https?:\/\/.*dsh.*\.com\/index\.php\/vod\/play\/.+\.html/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
            async parse() {
                this.media.setVideoUrl(player_aaaa.url);
            }
        },
    },
    {
        name: "大师兄电影网播放器",
        regex: /^https?:\/\/.*dsh.*\.com\/static\/player\/dplayer\.html/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                tryTime = TRY_TIME.maxParse;
            }
        },
    },
    {
        // ✅ https://www.twitch.tv/yulihong22
        name: "Twitch",
        home: ["https://www.twitch.tv"],
        regex: /^https:\/\/www\.twitch\.tv\/\w+/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                if (page.url.indexOf("/directory") != -1) {
                    tryTime = TRY_TIME.maxParse;
                    return;
                }
                this.media.setOther(
                    BEST_QUALITY.ytdlp[currentConfig.bestQuality]
                );
                this.media.setVideoUrl(this.ytDlpParser());
                this.media.setTitle("");
            }
        },
    },
    {
        // ✅ https://jiohub.top/watch/264
        // ✅ https://jiohub.top/watch/27686
        name: "JOJO",
        home: ["https://jiohub.top"],
        regex: /^https:\/\/jiohub\.top\/watch\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                let that = this;
                this.currentUrl = "";
                //拦截请求以更新Url
                const originOpen = XMLHttpRequest.prototype.open;
                XMLHttpRequest.prototype.open = function (
                    method,
                    url,
                    async,
                    user,
                    password
                ) {
                    originOpen.apply(this, arguments);
                    if (url.match(VIDEO_URL_REGEX)) {
                        that.currentUrl = url;
                    }
                };
            }
            async parse() {
                if (this.currentUrl) {
                    this.media.setVideoUrl(this.currentUrl);
                } else {
                    this.media.setVideoUrl(this.videoParser());
                }
            }
        },
    },
    {
        // ✅ https://www.agemys.org/play/20220403/1/1
        name: "AGE动漫",
        regex: /^https?:\/\/www\.agemys\.org\/play\/.+/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "AGE动漫播放器",
        regex: /^https?:\/\/vip\.sp-flv\.com:\d+\/\?url=.+/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(this.videoParser());
            }
        },
    },
    {
        // ✅ https://anime.girigirilove.com/playGV25353-1-1/
        name: "girigiri爱动漫",
        regex: /^https:\/\/anime\.girigirilove\.com\/play.+/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "girigiri爱动漫播放器",
        regex: /^https:\/\/anime\.girigirilove\.com\/addons\/dp\/player\/dp\.php\?.+/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                this.media.setVideoUrl(config.url);
            }
        },
    },
    {
        // ✅ https://www.cycdm01.top/watch/3388/1/1.html
        name: "次元城动漫",
        home: ["https://www.cycdm01.top"],
        regex: /^https:\/\/www\.cycdm01\.top\/watch\/.*/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addIframeListener();
            }
        },
    },
    {
        name: "次元城动漫播放器",
        regex: /^https:\/\/player\.cycdm01\.top\/\?url=.+/g,
        handler: class Handler extends BaseHandler {
            constructor() {
                super();
                this.addTopListener();
            }
            async parse() {
                let url = this.videoParser();
                if (url) {
                    let index = url.indexOf("?");
                    if (index != -1) {
                        url =
                            url.substring(0, index + 1) +
                            encodeURIComponent(url.substring(index + 1));
                    }
                    this.media.setVideoUrl(url);
                }
            }
        },
    },
    {
        name: "AList",
        regex: /^https?:\/\/[^\/]+\/.*\.(mp4|mkv|flv)/g,
        handler: class Handler extends BaseHandler {
            async parse() {
                let url = this.videoParser();
                let data = {
                    path: decodeURIComponent(location.pathname),
                    password: localStorage.getItem("browser-password"),
                };
                if (!url && tryTime < 3) {
                    $.ajax({
                        type: "POST",
                        url: `/api/fs/get`,
                        dataType: "json",
                        data: JSON.stringify(data),
                        xhrFields: {
                            withCredentials: true,
                        },
                        async: false,
                        contentType: "application/json",
                        success: function (res) {
                            if (res.code == 200) {
                                url = res.data.raw_url;
                            }
                        },
                    });
                }
                if (url) {
                    let index = url.indexOf("?");
                    if (index != -1) {
                        url =
                            url.substring(0, index + 1) +
                            encodeURIComponent(url.substring(index + 1));
                    }
                    this.media.setVideoUrl(url);
                    this.media.setTitle(document.title);
                }
            }
        },
    },
];
// 初始化
async function init(flag) {
    // 加载页面信息
    page = {
        host: window.location.host,
        url: window.location.href,
        isFullScreen: false,
    };
    // 清除 handler
    if (handler) {
        handler = undefined;
        if (document.getElementById(ID.buttonDiv)) {
            document.getElementById(ID.buttonDiv).style.display = "none";
        }
    }
    // 生成 handler
    for (let i = 0; i < websiteList.length; i++) {
        if (page.url.match(websiteList[i].regex)) {
            handler = new websiteList[i].handler();
            break;
        }
    }
    if (flag && page.url.startsWith("https://www.bilibili.com/festival/")) {
        await sleep(1500);
    }
    // 尝试解析页面视频
    if (handler) {
        tryTime = 0;
        while (tryTime < TRY_TIME.maxParse) {
            await sleep(tryTime * 1000 + 700);
            if (!handler.media.videoUrl) {
                try {
                    await handler.parse();
                } catch (error) {
                    console.log("Play-With-MPV：解析失败：" + error);
                }
            }
            tryTime++;
        }
    } else {
        console.log("Play-With-MPV：暂无此网页解析器（" + page.url + "）");
    }
}
// 开始执行
init();
setInterval(() => {
    if (handler) {
        if (handler.initCheck()) {
            init(true);
        }
    } else if (window.location.href != page.url) {
        init();
    }
}, TIME.refresh);